2

To get more type safety in our code base we have started to replace generic Strings, Ints etc. with type safe value classes, but I am struggling to get them working conveniently with the == operator and literals. Hopefully someone can help me out.

Our value classes are defined and used like this:

case class Name(value: String) extends AnyVal {} object Name { implicit def to(something:String): Name = Name(something) // convenience } case class Address(value: String) extends AnyVal {} object Address { implicit def to(something:String): Address = Address(something) // convenience } case class Person(name: Name, address: Address) { def move(newAddress: Address) = copy(address=newAddress) } val somebody = Person("Pete", "Street 1") somebody.move(Address("Street 2")) // allowed somebody.move(somebody.name) // not allowed, which is exactly what we want somebody.move("Street 2") // allowed by convenience 

Now, I would like them to compare "naturally" on their inner value:

Name("Pete") == "Pete" // should be true, but evaluates to False 

I can sort of fix this by overriding equals like this:

case class Name(value: String) extends AnyVal { override def equals(other: Any) = if (other.isInstanceOf[Name]) other.asInstanceOf[Name].value == this.value else if (other.isInstanceOf[String]) other == this.value else false } Name("Pete") == "Pete" // now evaluates to true 

However, this solution is not symmetric:

"Pete" == Name("Pete") // evaluates to false, because it is using String.equals 

I do not know how to fix this. Not even declaring an implicit conversion from Name to String helps (and I would much prefer not to have such a thing). Is it possible to do what I am trying to do?

EDIT: I probably failed to be clear on this, but I am not really looking for advice on software development. This is meant to be a technical question: Can it be done in Scala or not?

I have some reasons for doing what I have described, but sadly they relate to a codebase of tens of thousands of lines of Scala code, and cannot be conveyed in a short stack overflow question.

2
  • 5
    Are you sure they should evaluate to true? Isn't the point of value classes, that "Pete" and Name("Pete") are different entities? Commented Aug 10, 2017 at 13:00
  • 1
    Your partial solution won't compile for me due to limitations on value classes as documented here. (See limitation #4.) Commented Aug 10, 2017 at 16:36

2 Answers 2

4

I think, you should just get rid of your convenience implicits instead. They defeat the purpose:

 val john = Person("Street 1", "John") // mixed up order john.move("Pete") // Yup, I can "move" to a name ... 

Now, john is someone named "Street 1", and living at address "Pete". This isn't something that you want to be allowed after going with the trouble of defining all the value classes.

Sign up to request clarification or add additional context in comments.

Comments

2

I don't think == can be made to work in this situation. What you might do is define a different comparison operation.

case class Name(value: String) extends AnyVal { def is(n: Name): Boolean = value == n.value } 

You'll also have to broaden the scope of the implicit converter so that it can be accessed for these conversions.

implicit def toName(something:String): Name = Name(something) // not in object 

Now this works.

val somebody = Person("Pete", "Street 1") somebody.move(Address("Street 2")) // allowed somebody.move("Street 2") // allowed by convenience somebody.name is "Pete" // true "Pete" is somebody.name // true 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.