May mix-in traits extend case classes from a technical perspective? - scala

I read repeatedly on SO that case classes shall not be extended because a case class implements an equality method by default and that leads to issues of equality. However, if a trait extends a case class, is that also problematic?
case class MyCaseClass(string: String)
trait MyTrait extends MyCaseClass
val myCT = new MyCaseClass("hi") with MyTrait
I guess it boils down to the question, whether MyTrait is only forced to be mixable only into instantiations of MyCaseClass or whether MyTrait is inheriting the class members (field values and methods) of MyTrait and thus overwriting them. In the first case it would be okay to inherit from MyCaseClass, in the latter case it would not be okay. But which one is it?
To investigate, I advanced my experiment with
trait MyTrait extends MyCaseClass {
def equals(m: MyCaseClass): Boolean = false
def equals(m: MyCaseClass with MyTrait): Boolean = false
}
val myC = new MyCaseClass("hi")
myCT.equals(myC) // res0: Boolean = true
letting me to believe that the equals of MyCaseClass was used, not the one of MyTrait. This would suggest that it is okay for a trait to extend a case class (while it is not okay for a class to extend a case class).
However, I am not sure whether my experiment is legit. Could you shed some light on the matter?

Basically, trait can extend any class, so it's better to use them with regular classes (OOP-style).
Anyway, your equals contract is still broken regardless of your trick (note that standard Java's equals is defined on Any, that is used by default let's say in HashMap or even ==):
scala> trait MyTrait extends MyCaseClass {
| override def equals(m: Any): Boolean = false
| }
defined trait MyTrait
scala> val myCT = new MyCaseClass("hi") with MyTrait
myCT: MyCaseClass with MyTrait = MyCaseClass(hi)
scala> val myC = new MyCaseClass("hi")
myC: MyCaseClass = MyCaseClass(hi)
scala> myC.equals(myCT)
res4: Boolean = true
scala> myCT.equals(myC)
res5: Boolean = false
Besides, Hashcode/equals isn't the only reason...
Extending case class with another class is unnatural because case class represents ADT so it models only data - not behavior.
That's why you should not add any methods to it (in OOD terms case classes are designed for anemic approach). So, after eliminating methods - a trait that can only be mixed with your class becomes nonsense as the point of using traits with case classes is to model disjunction (so traits are interfaces here - not mix-ins):
//your data model (Haskell-like):
data Color = Red | Blue
//Scala
trait Color
case object Red extends Color
case object Blue extends Color
If Color could be mixed only with Blue - it's same as
data Color = Blue
Even if you require more complex data, like
//your data model (Haskell-like):
data Color = BlueLike | RedLike
data BlueLike = Blue | LightBlue
data RedLike = Red | Pink
//Scala
trait Color extends Red
trait BlueLike extends Color
trait RedLike extends Color
case class Red(name: String) extends RedLike //is OK
case class Blue(name: String) extends BlueLike //won't compile!!
binding Color to be only Red doesn't seem to be a good approach (in general) as you won't be able to case object Blue extends BlueLike
P.S. Case classes are not intended to be used in OOP-style (mix-ins are part of OOP) - they interact better with type-classes/pattern-matching. So I would recommend to move your complex method-like logic away from case class. One approach could be:
trait MyCaseClassLogic1 {
def applyLogic(cc: MyCaseClass, param: String) = {}
}
trait MyCaseClassLogic2 extends MyCaseClassLogic {
def applyLogic2(cc: MyCaseClass, param: String) = {}
}
object MyCaseClassLogic extends MyCaseClassLogic1 with MyCaseClassLogic2
You could use self-type or trait extends here but you can easily notice that it's redundant as applyLogic is bound to MyCaseClass only :)
Another approach is implicit class (or you can try more advanced stuff like type-classes)
implicit class MyCaseClassLogic(o: MyCaseClass) {
def applyLogic = {}
}
P.S.2 Anemic vs Rich. ADT is not precisely anemic model as it applies to immutable (stateless) data. If you read the article, Martin Fowler's approach is OOP/OOD which is stateful by default - that's what he assumes in most of the part of his article by implying that service layer and business layer should have separate states. in FP (at least in my practice) we still separate domain logic from service-logic, but we also separate operations from data (in every layer), which is another matter.

Extending case classes is a bad practice (generally), because it has concrete meaning -- "data container" (POJO / ADT). For example, Kotlin does not allow to do that.
Also, if you really want some trait to extend case class, you'd better use requires dependency (to avoid pitfalls with cases classes inheritance):
scala> case class A()
defined class A
scala> trait B { self: A => }
defined trait B
scala> new B{}
<console>:15: error: illegal inheritance;
self-type B does not conform to B's selftype B with A
new B{}

Related

How to combine ADTs in Scala?

I have two layers in my app: domain and application. Each layer has its own "error" ADT. For instance:
package com.domain.person
sealed trait DomainError
case object NoPermission extends DomainError
final case class Person(hasPermission: Boolean): Either[DomainError, ???] {
def doSomething() = {
if (!hasPermission)
Left(NoPermission)
else
...
}
}
and in my application layer (another package):
package com.application.person
sealed trait ApplicationError
case object PersonNotFound extends ApplicationError
case object UnexpectedFatalError extends ApplicationError
// and a function f :: Either ApplicationError Something
The issue is, since DomainError lives in another package, I can't just simply extend my ApplicationError trait:
sealed trait ApplicationError extends DomainError // compilation error
I could create yet another case object to wrap DomainError:
sealed trait ApplicationError
// n list of errors, and then:
final case class WrappedDomainError(d: DomainError) extends ApplicationError
but that solution is suboptimal at best.
And also, what if I want to be more specific in my doSomething() and, instead of returning a whole DomainError, a different subset?
doSomething :: Either DoSomethingErrors ???
I would have to account for all cases in each of my domain layer's functions.
Is there any way I can do a proper sum type in Scala?
Thanks
Wrapping your domain error in application error is not a bad idea, TBH. It's what I would've done in your situation. A few more options to consider:
make your DomainError and ApplicationError extends a common supertype Error, CommonError, Failure, etc. My personal preference is to extend Throwable - this way your error ASTs can become isomorphic to exceptions which can come in handy for Java interop reasons.
error channel also being composed of unions. Your final type will look somewhat like Either[ApplicationError Either DomainError, A]. It's a bit mouthful but you can make it look less ugly by introducing aliases.
type Result[+A] = Either[ApplicationError Either DomainError, A]
def doSomething: Result[???]
replace either with your own AST or use scalaz or other library's alternatives to Either3
sealed trait Result[+A]
case class Success[A](a: A) extends Result[A]
case class ApplicationErr(err: ApplicationError) extends Result[Nothing]
case class DomainErr[A](err: DomainErr) extends Result[Nothing]
def doSomething: Result[???]
Interpret DomainErrors into ApplicationErrors
val maybeDomainErrorVal: Either[DomainError, ???] = ???
val maybeApplicationErrorVal: Either[ApplicationError, ???] =
maybeDomainErrorVal.leftMap {
case NoPermission => UnexpectedFatalError
}

Type Class Pattern alternative for enriching ADT with methods in Scala

I wrote a basic Algebraic Data Type defined as follows
sealed trait FruitĀ {def name: String}
case class Apple(name: String) extends Fruit
case class Orange(name: String, color: String) extends Fruit
What I'd love to do is to define a common method across Apple and Orange. So I decided to offer this feature by a Type Class pattern.
sealed trait ServingFruit[T] {
def peel(fruit: T): String
}
object FruitManager {
def retrievePeelColor[T: ServingFruit](fruit: T): String =
implicitly[ServingFruit[T]].peel(fruit)
implicit object ApplePeelColor extends ServingFruit[Apple] {
def peel(fruit: Apple): String = "GREEN"
}
implicit object OrangePeelColor extends ServingFruit[Orange] {
def peel(fruit: Orange): String = fruit.color
}
}
For a necessary (and unfortunate) constraint I have to handle fruits as bounded instances of the shared base trait Fruit
def myCodeMethod[F <: Fruit](fruit: F, out: String) = {
import FruitManager._
FruitManager.retrievePeelColor(fruit)
}
And it brings me to the following (somehow expected) exception.
could not find implicit value for evidence parameter of type my.path.to.fruit.ServingFruit[F] [error]FruitManager.retrievePeelColor(fruit)
Then, AFAIU here and here Type Class Pattern is type independent and perhaps the latter is not well fitting my scenario.
The point is I'm struggling on figuring out a valuable solution to integrate my ADT with a common method available also to the base trait and - at the same time - I'd like to avoid providing the methods within the ADT (I'd try to stay FP oriented) and to use such workarounds as to add an additional Fruit converter to my Type Class.
Any help'd be really appreciated, thank you.
Andrea
You need to make the type class witness passed into retrievePeelColor as an implicit parameter:
scala> def retrievePeelColor[T](fruity: T)(implicit peeler: ServingFruit[T]) = peeler.peel(fruity)
retrievePeelColor: [T](fruity: T)(implicit peeler: ServingFruit[T])String
scala> retrievePeelColor(Apple("granny smith"))
res0: String = GREEN
scala> retrievePeelColor(Orange("bloody", "RED"))
res1: String = RED
And as for the design: I'm not an experienced design guy, but I wouldn't say that having some methods in a sealed trait isn't "FP style". If only fruits are peelable, and there are finitely many, it's OK to have one place for peel, IMHO (by this I'm talking about a method in Fruit matching on this, or a static member in the Fruit companion object).

What is the preferable way to define ADT in Scala - OO vs FP

After some discussions i would like to emphasise, that the question is related of using inheritance as in OOP over Product/Sum type as in FP or vice versa. What you prefer and what are your suggestions! So...
I have some doubts - what is the cleaner (clearer) way to define the following:
For example I prefer
sealed trait Shape
case class Square(length: Int) extends Shape
case class Rhombus(length: Int) extends Shape
instead of
sealed abstract class Shape(val length: Int)
case class Square(override val length: Int) extends Shape(length)
case class Rhombus(override val length: Int) extends Shape(length)
but then
def draw(shape: Shape): String = shape match {
case Square(length) => length.toString
case Rhombus(length) => length.toString
}
instead of
def draw(shape: Shape): String = shape.length.toString
Definition of the types looks in the first approach less verbose and more consistent but if i need to have a generic logic for all shapes, like draw method, second approach looks clearer.
This would be the prefered way:
sealed trait Shape {
def length: Int
}
case class Square(length: Int) extends Shape
case class Rhombus(length: Int) extends Shape
Here are the few non-idiomatic things in your code:
abstract class are primarely a java compatibility thing, use traits when possible
override a non implemented field feels wrong
val in interfaces should be avoided, always use defs for the abstract, these can be refined with something else
From this book https://leanpub.com/fpmortals/
We can also use a sealed trait in place of a sealed abstract class but there are binary compatibility advantages to using abstract class. A sealed trait is only needed if you need to create a complicated ADT with multiple inheritance.
It's not bad suggestion, but for me it's more inheritance over Product type. With trait it's maybe more a mixin, but still! length is then a part of all shapes even if you don't need it.
If you don't need it for all shapes, then you can't have
def draw(shape: Shape): String = shape.length.toString
in the first place, and not getting it isn't a drawback. But you can also have
sealed trait Shape
sealed trait HasLength extends Shape {
def length: Int
}
// Shape optional, but may be clearer for this case
case class Square(length: Int) extends Shape with HasLength
case class Rhombus(length: Int) extends Shape with HasLength
def draw(shape: Shape): String = shape match {
case shape: HasLength => shape.length.toString
case ... => ... // handle those shapes which don't have length
}

get the class of "case class" from it's companion

I have the following code:
trait Base[A,B] {
def name: String
}
trait BaseCompanion[A,B] {
def classOfBase: Class[_ <: Base[A,B]] // Can I implement something generic here ?
}
case class First(name: String) extends Base[Int,String]
object First extends BaseCompanion[Int,String] {
override def classOfBase: Class[_ <: Base[Int, String]] = classOf[First] // Can this part be generic ?
}
I don't want to override the classOfBase method in every concrete class that will extend BaseCompanion.This can be achieved by changing BaseCompanion to:
abstract class BaseCompanion[A,B, CLAZZ <: Base[A,B] : ClassTag] {
def classOfBase: Class[CLAZZ] = classTag[CLAZZ].runtimeClass.asInstanceOf[Class[CLAZZ]]
}
object First extends BaseCompanion[Int,String,First]
But I don't really like this solution, is there a way to do this without changing the signature of BaseCompanion and implement something generic inside the it ?
By the way today Companion object of any case class "defines" apply(...) method. Given the example above there will be a method similar to this:
abstract class BaseCompanion[A,B, CLAZZ <: Base[A,B] : ClassTag] {
def classOfBase: Class[CLAZZ] = classTag[CLAZZ].runtimeClass.asInstanceOf[Class[CLAZZ]]
/* No need to implement in the companion of a case class that extends Base */
def apply(name: String): Base[A,B]
}
The return type of this apply method is known to the Companion perhaps there is a way to use this information.
Yes, it can be done with this change:
def classOfBase = this.getClass.getMethods.find(_.getName == "apply").get.
getReturnType.asInstanceOf[Class[_ <: Base[A,B]]]
Note that this assumes there is precisely one apply method. In practice, you should check this assumption.
Assuming that if what you want would work, the following would be possible:
case class First(name: String) extends Base[Int, String]
object First extends BaseCompanion[Int, String]
assert(First.baseClass == classOf[First])
Now if that would work, what would stop you from doing the following?
class Second extends BaseCompanion[Int, String]
val second = new Second
println(second.baseClass)
or
// case class Third not defined
object Third extends BaseCompanion[Int, String]
println(Third.baseClass)
What would that result in? Second and Third are not a companion object with an associated class. Second is a class itself!
The problem is that you cannot force your BaseCompanion trait to only be inherited by something that is a companion object. BaseCompanion therefore cannot use the special relation that companion objects and associated classes have. If you want information about the associated class with a companion object, you have to give BaseCompanion that information manually in the definition of your companion object.
The Scala compiler won't allow you to do this. You can work around this with reflection if you want, but whatever solution remains that accomplishes this has to take into account that you are effectively creating unpredictable runtime behaviour. In my opinion, it's best to just help the compiler figure it out, and supply the proper information at compile time.

Scala: lock extended class

I'd like to "lock" a class, which is extended from a trait. Is it possible in Scala?
For example I have:
trait A {
val boris: String
val john: String
val number: Int
}
class B extends A {
// do something with these values
}
but can I ensure, that in class B no new values will be added if those aren't declared in trait A?
Thanks for your answers.
You cannot.
But if you simply mark the trait as sealed and provide a default implementation:
sealed trait A { val boris: String }
final class B(val boris: String) extends A {}
then people are free to create implicit value classes that make it look like new functionality has been added (except without actually creating the class):
implicit class MyB(val underlying: B) extends AnyVal {
def sirob = underlying.boris.reverse
}
(new B("fish")).sirob // "hsif"
You can also let the classes take a type parameter as a marker if you want to keep them straight at compile-time (though not runtime):
sealed trait A[T] { val boris: String }
final class B[T](val boris: String) extends A[T] {}
implicit class MyB(val underlying: B[Int]) extends AnyVal {
def sirob = underlying.boris.reverse
}
(new B[Int]("fish")).sirob // "hsif"
(new B[Char]("fish")).sirob // error: value sirob is not a member of B[Char]
So you could--especially with 2.10--simply lock everything and let users enrich the original interface this way.
I'm not sure if this covers your intended use case, though; it doesn't provide any inheritance.
Based on your example and my guess at what you are actually trying to do, you may want to consider just using case classes.
Extending a case class is generally avoided (I think it will spit out deprecation warnings if you try), so that will prevent people from wanting to extend your class in order to add functionality.
Translating your example into a case class:
case class A (boris: String, john: String, number: Int)
Then instead of extending A to change its values, you'd just make a new instance, e.g.
val a2 = someOtherA.copy(john="Doe")