Reputation: 4231
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 ?
Upvotes: 2
Views: 761
Reputation: 6182
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.
Upvotes: 2
Reputation: 27356
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
.
Upvotes: 2
Reputation: 1924
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)
Upvotes: 0