Scala: Overriding equals for value classes - scala

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.

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.

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

Related

case class with logic what is the idiomatic way

What is the FP idiomatic way for this: let's say I have this
trait Name
object Name{
def apply(name: String): Name = {
if (name.trim.isEmpty || name.trim.length < 3)
InvalidName
else
ValidName(name.trim)
}
}
case object InvalidName extends Name
case class ValidName(name:String) extends AnyVal with Name
Now I have some helper functions such as
def split = name.splitAt(" ")
//some more functions
which way is more idiomatic:
Put them in the case class it self but that some how makes the case class to contain some logic however I can do something like :
val n = ValidName("john smith")
val (first, last) = n.split
Put them in dedicated object but then the method will look like
def split(n: ValidName) = n.name.splitAt(" ")
Create an object with implicit class that will accept Name and will call the methods
What do you think ?
There is no major issue with adding logic to a case class, especially when it is just extracting the data in a different format. (It becomes problematic when you add data members to a case class).
So I would do option 1 and not worry about it!
In response to the comments, case class is actually just a shortcut for creating a class with a whole bunch of useful pre-implemented methods. In particular, the unapply method allows a case class to be used in pattern matching, and equals does an element-wise comparison of the fields of two instances.
And there are a bunch of other methods to extract the data from the case class in different ways. The most obvious are toString and copy, but there are others like hashCode and all the stuff inherited from Product, such as productIterator.
Since a case class already has methods to extract the data in useful ways, I see no objection to adding your split method as another way of extracting data from the case class.
More idiomatic:
case class Name private (name: String) {
lazy val first :: last :: Nil = name.split(" ").toList
}
object Name {
def fromString (name: String): Either[String, Name] = {
if (name.trim.isEmpty || name.trim.length < 3) Left("Invalid name")
else Right(new Name(name.trim))
}
}
Or maybe this:
case class Name (first: String, last: String) {
lazy val fullName = s"$first $last"
}
object Name {
def fromString (name: String): Either[String, Name] = {
if (name.trim.isEmpty || name.trim.length < 3) Left("Invalid name")
else {
val first :: last :: Nil = name.split(" ").toList
Right(new Name(first, last))
}
}
}
In scala it's more idiomatic to represent failure cases through the use of Either than through inheritance. If you have an instance of N, you can't call any functions on it, you'll probably have to pattern match it. But a type like Either already comes with functions like map, fold, etc. that makes it easier to work with.
Having a private constructor helps ensure that you can only create a valid Name because the only way to create one is through the fromString method.
DO NOT use implicits for this. There's no need and would only make for confusing code. Not really what implicits are for.
I think it depends on context. In this case, if most of the methods you are using are just slight tweaks to String methods, you might want to consider a fourth option:
case class Name(name: String)
implicit def NameToString(n: Name) = n.name
Name("Iron Man").split(" ") // Array(Iron, Man)

Scala - Enumeration with value parametrized

I'm wondering if its possible to have Enumerations with one value parametrized.
Something like:
object Animal extends Enumeration {
val Dog = Value("dog")
val Cat = Value("cat")
val Other = Value(?)
def create(value: String): Animal.Value = {
Animal.values.find(_.toString == value).getOrElse(Other(value))
}
}
And, for use, something like:
create("dog") // will return Animal.Dog
create("giraffe") // will return Animal.Other
create("dog").toString // will return "dog"
create("giraffe").toString // will return "giraffe"
That is, to be able to have some values typed, but to leave one free.
Thanks!!!
Lucas.
I have to apologize for jumping the gun. I was thinking in Java terms, where an enum is a very rigid thing. Scala, however, is a bit more flexible in that regard. Enumeration does not stop us from extending the enumeration class ourselves.
Disclaimer: This is probably not a good idea. It works, but I don't know how it will behave with respect to serialization or the other nice properties that ordinary enumerations have. So if it works for you, great! But I can't promise that it's a good solution.
object Animal extends Enumeration {
val Dog = Value("dog")
val Cat = Value("cat")
def create(value: String): Animal.Value = {
Animal.values.find(_.toString == value).getOrElse(OtherAnimal(value))
}
}
// Extending the enumeration class by hand and giving it a `String` argument.
case class OtherAnimal(name: String) extends Animal.Value {
override def id = -1
override def toString = name
}
println(Animal.create("dog").toString) // dog
println(Animal.create("cat").toString) // cat
println(Animal.create("giraffe").toString) // giraffe

Scala casting and avoiding asInstanceOf

In the following code, is it possible to reformulate without using asInstanceOf? I found some styleguide suggestions that asInstanceOf/isInstanceOf should be avoided, and I managed to clean up my code except for the usage shown below.
I did find a duplicate question here, but it didn't really help me for this particular case, or I'm just too much of a beginner to translate it to my own use case.
trait pet {}
class dog extends pet {
def bark: String = {
"WOOF"
}
}
def test(what: pet) : String =
{
what match {
case _:dog =>
val x = what.asInstanceOf[dog]
x.bark
}
}
test(new dog())
I tried for example:
val x = what : dog
but that doesn't seem to work.
You can just specify in case section that you expect dog object:
case x: dog => x.bark
But now you might receive scala.MatchError if non-dog object will be passed to your method. So you need to add default case with desirable behavior like this:
case _ => "unknown pet"

Scala 2.9: Parsers subclass not recognizing "Elem" override?

I wrote a parser to act as a lexer. This lexer parses a file and returns a list of tokens, each of which is a case class or object that extends a common trait.
I am now trying to write a parser for the output of the lexer, but I have hit a very confusing snag. The parser is happy to implicitly cast my case objects, but throws a fit if I even try to call apply(classHere) manually.
The following is a simplified version of my code:
// CODE
trait Token
case class StringWrapperIgnoresCase(val string: String) {
private case class InnerWrapper(s: String)
lazy val lower = string.toLowerCase
override lazy val hashCode = InnerWrapper(lower).hashCode
override def equals(that: Any) =
that.isInstanceOf[StringWrapperIgnoresCase] &&
lower == that.asInstanceOf[StringWrapperIgnoresCase].lower
}
case class ID(val text: String)
extends StringWrapperIgnoresCase(text)
with Token {
override def toString = "ID(" + text + ")"
}
case object PERIOD extends Token
object Parser extends Parsers {
type Elem = Token
def doesWork: Parser[Token] = PERIOD
def doesNotWork: Parser[Token] = ID
}
The compiler reports the following message about doesNotWork:
// ERROR MESSAGE
type mismatch; found : alan.parser.ID.type (with underlying type object alan.parser.ID) required: alan.parser.Parser.Parser[alan.parser.Token]
How can I fix this?
Update: It wasn't clear to me from your question exactly what you were asking, but now that you've specified that you want a parser that matches any ID in your answer, here's a more idiomatic solution:
val id: Parser[ID] = accept("ID", { case i: ID => i })
Here you've provided a description of what the parser wants (for error messages) and a partial function with IDs as its domain. You could also use the acceptIf version that xiefei provides in a comment on your answer.
When you refer to a case class (as opposed to a case object) without a parameter list, you get the automatically generated companion object, which is not an instance of the class itself. Consider the following:
sealed trait Foo
case class Bar(i: Int) extends Foo
case object Baz extends Foo
Now Baz: Foo is just fine, but Bar: Foo will give an error very similar to what you're seeing.
Note also that what's happening here isn't strictly casting. The Parsers trait has a method with the following signature:
implicit def accept(e: Elem): Parser[Elem]
When you write this:
def doesWork: Parser[Token] = PERIOD
You're trying to type an Elem as a Parser[Elem], and the implicit conversion kicks in (see section 7.3 of the spec for more information about implicit conversions). When you write this, on the other hand:
def doesNotWork: Parser[Token] = ID
You're trying to type the ID companion object (which has type ID.type, not ID or Token, and therefore not Elem) as a Parser[Elem], and there's no implicit conversion that makes this possible.
You're probably better off writing out accept(PERIOD) and accept(ID("whatever")), for now, at least, and obeying the deprecation warning that says the following when you try to compile your code:
Case-to-case inheritance has potentially dangerous bugs which are
unlikely to be fixed.
Using what TravisBrown and drstevens have said, I have added a new production to the parser:
def id = {
acceptIf(
_ match {
case ID(_) => true
case _ => false
}
)("'ID(_)' expected but " + _ + " found")
}
def nowWorks = id
I won't accept this as the answer for the time being to allow someone to provide a more elegant solution than this. This looks a bit messy for my tastes, and I'm certain someone more accustomed to the functional programming approach will turn this into an elegant one-liner.

Read case class object from string in Scala (something like Haskell's "read" typeclass)

I'd like to read a string as an instance of a case class. For example, if the function were named "read" it would let me do the following:
case class Person(name: String, age: Int)
val personString: String = "Person(Bob,42)"
val person: Person = read(personString)
This is the same behavior as the read typeclass in Haskell.
dflemstr answered more towards setting up the actual read method- I'll answer more for the actual parsing method.
My approach has two objects that can be used in scala's pattern matching blocks. AsInt lets you match against strings that represent Ints, and PersonString is the actual implementation for Person deserialization.
object AsInt {
def unapply(s: String) = try{ Some(s.toInt) } catch {
case e: NumberFormatException => None
}
}
val PersonRegex = "Person\\((.*),(\\d+)\\)".r
object PersonString {
def unapply(str: String): Option[Person] = str match {
case PersonRegex(name, AsInt(age)) => Some(Person(name, age))
case _ => None
}
}
The magic is in the unapply method, which scala has syntax sugar for. So using the PersonString object, you could do
val person = PersonString.unapply("Person(Bob,42)")
// person will be Some(Person("Bob", 42))
or you could use a pattern matching block to do stuff with the person:
"Person(Bob,42)" match {
case PersonString(person) => println(person.name + " " + person.age)
case _ => println("Didn't get a person")
}
Scala does not have type classes, and in this case, you cannot even simulate the type class with a trait that is inherited from, because traits only express methods on an object, meaning that they have to be "owned" by a class, so you cannot put the definition of a "constructor that takes a string as the only argument" (which is what "read" might be called in OOP languages) in a trait.
Instead, you have to simulate type classes yourself. This is done like so (equivalent Haskell code in comments):
// class Read a where read :: String -> a
trait Read[A] { def read(s: String): A }
// instance Read Person where read = ... parser for Person ...
implicit object ReadPerson extends Read[Person] {
def read(s: String): Person = ... parser for Person ...
}
Then, when you have a method that depends on the type class, you have to specify it as an implicit context:
// readList :: Read a => [String] -> [a]
// readList ss = map read ss
def readList[A: Read] (ss: List[String]): List[A] = {
val r = implicitly[Read[A]] // Get the class instance of Read for type A
ss.map(r.read _)
}
The user would probably like a polymorphic method like this for ease of use:
object read {
def apply[A: Read](s: String): A = implicitly[Read[A]].read(s)
}
Then one can just write:
val person: Person = read[Person]("Person(Bob,42)")
I am not aware of any standard implementation(s) for this type class, in particular.
Also, a disclaimer: I don't have a Scala compiler and haven't used the language for years, so I can't guarantee that this code compiles.
Starting Scala 2.13, it's possible to pattern match a Strings by unapplying a string interpolator:
// case class Person(name: String, age: Int)
"Person(Bob,42)" match { case s"Person($name,$age)" => Person(name, age.toInt) }
// Person("Bob", 42)
Note that you can also use regexes within the extractor.
Which in this case, helps for instance to match on "Person(Bob, 42)" (age with a leading space) and to force age to be an integer:
val Age = "[ ?*](\\d+)".r
"Person(Bob, 42)" match {
case s"Person($name,${Age(age)})" => Some(Person(name, age.toInt))
case _ => None
}
// Person = Some(Person(Bob,42))
The answers on this question are somewhat outdated. Scala has picked up some new features, notably typeclasses and macros, to make this more easily possible.
Using the Scala Pickling library, you can serialize/deserialize arbitrary classes to and from various serialization formats:
import scala.pickling._
import json._
case class Person(name: String, age: Int)
val person1 = Person("Bob", 42)
val str = person1.pickle.value // { tpe: "Person", name: "Bob", age: 42 }
val person2 = JSONPickle(str).unpickle[Person]
assert(person1 == person2) // Works!
The serializers/deserializers are automatically generated at compile time, so no reflection! If you need to parse case classes using a specific format (such as the case class toString format), you can extend this system with your own formats.
The uPickle library offers a solution for this problem.
Scala uses Java's serialization stuff, with no String representation.