Extracting logic out of the case class and keeping them clean ADT


#1

I can across question in stackoverflow (cannot find the link at the moment, my apology), and I wonder if you need to extract some data from a member e.g

case class Name(fullName: String) {
  def asNamesAccordingToBusinessLogic:(String, String) = fullName.splitAt(" ") //hmm some closer not very FP
}
would you rather keep the case class as clean ADT and put logic in a companion object e.g
object Name{
 def getNamesAccordingToBusinessLogic(n: Name):(String, String) = n.fullName.splitAt(" ") 
}

but it seems to be overhead and not very elegant.
or you rather keep it in the case class for simplicity ?
of course you can also create some implicit class that will do the conversion but it seems to be discourage since I rather not keep biz logic in implicit
WDYT ?


#2

I think this is very much a style issue and would be addressed by the style guide of the company. The first option is clearly more OO and the second leans toward the functional. Personally, I find the first one to be clearer and more concise, but if you are working in an organization or on a code-base that really focuses on having a functional style, then you would probably want to go with the latter.


#3

I don’t see the point of the second option. If the function getNamesAccordingToBusinessLogic applies to exactly one instance of the class, then it seems to me that the function should be a method of the class. The companion object is not for functions that take only the implied “this” as an argument.

If you want to be more functional, you should also consider creating a case class that contains the given name and family name (and perhaps also a middle name or names?). Then have your function return that type rather than a tuple of Strings. (You might want to rename your “Name” class to “FullName” and call he new class “Name”.)


#4

After thinking about it a bit more, I would suggest that you get rid of the “Name” class and replace it with a case class that clearly shows the structure of the name. For example,

case class Name(surname: String, givenName: String,
middleNames: List[String] = List(), suffix: String="")

Then you can have simple methods that return the full name as a String according to various conventions, such as

John Doe
John Doe, Jr.
John F. Doe
John Frank Doe
Doe, John F.

It is better to have the structure encoded in the case class than to rely on String parsing to determine the structure, which is error prone. Some surnames have spaces, for example. The suffixes could be restricted to a discrete list, by the way.


#5

I would keep in mind that Scala is an object-functional mixed language. It’s not trying to be a ‘pure functional’ language. Take advantage of Scala’s OO abilities; put members that need access to the instance, in the class definition. Take advantage of traits to decouple data structures from things that use them. Etc.

Fighting against the grain of Scala will just lead to awkward-looking code. Going with the grain will lead to code that flows easily.


#6

Got you, you say that we can have several functions with those tweaks , and create case class for each. would you place them in an object that will contain those factory methods ? on the other to keep them in the original class so class Name will have a method .getNames:FullName (where FullName is of type FullName(first: String, last: String) ) doesn’t seem right. I would except that factory methods of class Name will return class Name
I agree that this is a design issue. on one hand this is just operation on one of the members I’m afraid that creating case class for each method will be an overhead. on the other hand we want to keep the ADT as clean of biz logic as much as possible


#7

The different formats could be implemented with simple methods, with toString used for the one that is expected to be the most commonly used. For example,

case class Name(surname: String, givenName: String,
middleNames: List[String] = List(), suffix: String="") {

  override def toString = s"$givenName $middleNames $surname, $suffix"
  def lastFirst = s"$surname, $givenName $middleNames, $suffix"
  ...
  }

Since I showed middleNames as a List, you may actually need something like middleNames.mkString(" ").