I recently stumbled on a rather awesome piece of code from Travis Brown # Iteration over a sealed trait in Scala?. It turns Scala's sealed trait + case class/object based ADTs into something that is closer to true enums by allowing enumeration of the ADT "constructors" (in Haskell's terms). So I removed 2 of the 3 deprecation warnings Travis' code was producing and ended up with http://pastebin.com/L1gYGJWh which I then used as follows:
sealed trait KeywordType
case object Crime extends KeywordType
case object Gambling extends KeywordType
case object Porn extends KeywordType
object KeywordType {
/** Maps e.g. "Crime" to Crime */
def withName(x: String): Option[KeywordType] =
adt.enumerate[KeywordType].find(_.toString === x)
}
However, I quickly needed to reuse the same bit of withName logic so I tried to write the following ADT base trait:
trait ADT[T] {
def all: Set[T] = enumerate[T]
def withName(name: String): Option[T] =
all.find(_.toString === name)
}
However, this does not compile giving:
Can only enumerate values of a sealed trait or class
Although I understand why this error occurs, I do not have a clue as to how to go about telling the type system to "defer" that check until something actually inherits from ADT.
Is this even possible with the current macro system in Scala 2.11.2 or do I have to find a (partial) workaround?
Related
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
}
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).
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{}
I have the following relations:
trait Instrument
trait EquityOption extends Instrument { ... }
case class CallEquityOption(...) extends EquityOption
case class PutEquityOption(...) extends EquityOption
trait Priceable[I <: Instrument] { def price(I : Instrument) }
I can use exactly the same implementation of Priceable for the case classes CallEquityOptionand PutEquityOption. By having a match case to differentiation between the Call... and Put.... However, if I try to implement it directly as Priceable[EquityOption] under object EquityOption, the implicit cannot be found since it doesn't exactly match the type.
How can I make it work without needing to duplicate code?
You'll have to prove that you can provide an instance for every subtype of EquityOption.
implicit def allEquityOptions[T <: EquityOption]: Pricable[T] = ???
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.