Here is a simplified version of my code.
How can I avoid to call asInstanceOf (because it is a smell for a poorly design solution) ?
sealed trait Location
final case class Single(bucket: String) extends Location
final case class Multi(buckets: Seq[String]) extends Location
#SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
class Log[L <: Location](location: L, path: String) { // I prefer composition over inheritance
// I don't want to pass location to this method because it's a property of the object
// It's a separated function because there is another caller
private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
def getPaths(): Seq[String] =
location match {
case _: Single => Seq(this.asInstanceOf[Log[_ <: Single]].getSinglePath())
case m: Multi => m.buckets.map(bucket => s"fs://${bucket}/$path")
}
}
Try a type class
class Log[L <: Location](location: L, val path: String) {
def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
def getPaths()(implicit gp: GetPaths[L]): Seq[String] = gp.getPaths(location, this)
}
trait GetPaths[L <: Location] {
def getPaths(location: L, log: Log[L]): Seq[String]
}
object GetPaths {
implicit val single: GetPaths[Single] = (_, log) => Seq(log.getSinglePath())
implicit val multi: GetPaths[Multi] = (m, log) => m.buckets.map(bucket => s"fs://${bucket}/${log.path}")
}
Normally a type class is a compile-time replacement for pattern matching.
I had to make getSinglePath public and path a val in order to provide access to them inside GetPaths. If you don't want to do so you can make the type class nested into Log
class Log[L <: Location](location: L, path: String) {
private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
def getPaths()(implicit gp: GetPaths[L]): Seq[String] = gp.getPaths(location)
private trait GetPaths[L1 <: Location] {
def getPaths(location: L1): Seq[String]
}
private object GetPaths {
implicit def single(implicit ev: L <:< Single): GetPaths[L] = _ => Seq(getSinglePath())
implicit val multi: GetPaths[Multi] = _.buckets.map(bucket => s"fs://${bucket}/$path")
}
}
Actually we don't have to pass location explicitly and we don't need L1
class Log[L <: Location](location: L, path: String) {
private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
def getPaths()(implicit gp: GetPaths): Seq[String] = gp.getPaths()
private trait GetPaths {
def getPaths(): Seq[String]
}
private object GetPaths {
implicit def single(implicit ev: L <:< Single): GetPaths = () => Seq(getSinglePath())
implicit def multi(implicit ev: L <:< Multi): GetPaths = () => location.buckets.map(bucket => s"fs://${bucket}/$path")
}
}
Now GetPaths is zero-parameter type class and slightly similar to magnet pattern.
Related
I have the following code:
sealed trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
trait Show[A] {
def show(a: A): String
}
class Processor[A](a: A) {
def print(implicit S: Show[A]): Unit = println(S.show(a))
}
implicit val showCat: Show[Cat] = c => s"Cat=${c.name}"
implicit val showDog: Show[Dog] = d => s"Dog=${d.name}"
val garfield = Cat("Garfield")
val odie = Dog("Odie")
val myPets = List(garfield, odie)
for (p <- myPets) {
val processor = new Processor(p)
processor.print // THIS FAILS AT THE MOMENT
}
Does anyone know of a nice way to get that line processor.print working?
I can think of 2 solutions:
pattern match the p in the for loop.
create an instance of Show[Animal] and pattern match it against all its subtypes.
But I'm wondering if there's a better way of doing this.
Thanks in advance!
Compile error is
could not find implicit value for parameter S: Show[Product with Animal with java.io.Serializable]
You can make Animal extend Product and Serializable
sealed trait Animal extends Product with Serializable
https://typelevel.org/blog/2018/05/09/product-with-serializable.html
Also instead of defining implicit Show[Animal] manually
implicit val showAnimal: Show[Animal] = {
case x: Cat => implicitly[Show[Cat]].show(x)
case x: Dog => implicitly[Show[Dog]].show(x)
// ...
}
you can derive Show for sealed traits (having instances for descendants) with macros
def derive[A]: Show[A] = macro impl[A]
def impl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val subclasses = typA.typeSymbol.asClass.knownDirectSubclasses
val cases = subclasses.map{ subclass =>
cq"x: $subclass => _root_.scala.Predef.implicitly[Show[$subclass]].show(x)"
}
q"""
new Show[$typA] {
def show(a: $typA): _root_.java.lang.String = a match {
case ..$cases
}
}"""
}
implicit val showAnimal: Show[Animal] = derive[Animal]
or Shapeless
implicit val showCnil: Show[CNil] = _.impossible
implicit def showCcons[H, T <: Coproduct](implicit
hShow: Show[H],
tShow: Show[T]
): Show[H :+: T] = _.eliminate(hShow.show, tShow.show)
implicit def showGen[A, C <: Coproduct](implicit
gen: Generic.Aux[A, C],
show: Show[C]
): Show[A] = a => show.show(gen.to(a))
or Magnolia
object ShowDerivation {
type Typeclass[T] = Show[T]
def combine[T](ctx: CaseClass[Show, T]): Show[T] = null
def dispatch[T](ctx: SealedTrait[Show, T]): Show[T] =
value => ctx.dispatch(value) { sub =>
sub.typeclass.show(sub.cast(value))
}
implicit def gen[T]: Show[T] = macro Magnolia.gen[T]
}
import ShowDerivation.gen
or Scalaz-deriving
#scalaz.annotation.deriving(Show)
sealed trait Animal extends Product with Serializable
object Show {
implicit val showDeriving: Deriving[Show] = new Decidablez[Show] {
override def dividez[Z, A <: TList, ShowA <: TList](tcs: Prod[ShowA])(
g: Z => Prod[A]
)(implicit
ev: A PairedWith ShowA
): Show[Z] = null
override def choosez[Z, A <: TList, ShowA <: TList](tcs: Prod[ShowA])(
g: Z => Cop[A]
)(implicit
ev: A PairedWith ShowA
): Show[Z] = z => {
val x = g(z).zip(tcs)
x.b.value.show(x.a)
}
}
}
For cats.Show with Kittens you can write just
implicit val showAnimal: Show[Animal] = cats.derived.semi.show
The thing is that garfield and odie in List(garfield, odie) have the same type and it's Animal instead of Cat and Dog. If you don't want to define instance of type class for parent type you can use list-like structure preserving types of individual elements, HList garfield :: odie :: HNil.
For comparison deriving type classes in Scala 3
How to access parameter list of case class in a dotty macro
The most general solution is to just pack the typeclass instances in at the creation of myPets, existentially
final case class Packaged[+T, +P](wit: T, prf: P)
type WithInstance[T, +P[_ <: T]] = Packaged[U, P[U]] forSome { type U <: T }
implicit def packageInstance[T, U <: T, P[_ <: T]]
(wit: U)(implicit prf: P[U])
: T WithInstance P
= Packaged(wit, prf)
val myPets = List[Animal WithInstance Show](garfield, odie)
for(Packaged(p, showP) <- myPets) {
implicit val showP1 = showP
new Processor(p).print // note: should be def print()(implicit S: Show[A]), so that this can be .print()
}
I want to write a type class, to add some behavior to a generic type. However, I cannot figure out how to do it; I keep running into the error below.
Imagine you have a generic type MyList[A]:
trait MyList[A]
object MyList {
case class Empty[A]() extends MyList[A]
case class Node[A](value: A, next: MyList[A]) extends MyList[A]
}
Now you want to add some behavior to this class, e.g. to convert it into a Stream[A]. A type class based extension would seem appropriate:
// inspired by https://scalac.io/typeclasses-in-scala
trait MyListExt[T, A <: MyList[T]] {
def stream(a: A): Stream[T]
}
object MyListExt {
def apply[T, A <: MyList[T]](implicit a: MyListExt[T, A]): MyListExt[T, A] = a
object ops {
implicit class MyListExtOps[T, A <: MyList[T]](val a: A) extends AnyVal {
def stream(implicit ext: MyListExt[T, A]): Stream[T] = ext.stream(a)
}
}
private type T0
implicit val myListToStreamImpl: MyListExt[T0, MyList[T0]] = new MyListExt[T0, MyList[T0]] {
override def stream(a: MyList[T0]): Stream[T0] = {
def fold[T1](l: MyList[T1], acc: Stream[T1]): Stream[T1] = l match {
case MyList.Empty() => acc
case MyList.Node(value, next) => fold(next, acc :+ value)
}
fold(a, Stream.empty)
}
}
}
When I now try to use this type class in my code, I get the following error at l.stream:
No implicits found for parameter ext: MyListExt[T_, MyList[Int]]
object MyListTest {
def main(args: Array[String]): Unit = {
import MyListExt.ops._
val l: MyList[Int] = MyList.Node(1, MyList.Node(2, MyList.Node(3, MyList.Empty())))
l.stream.foreach(println)
}
}
What am I doing wrong, or how can I get my l.stream to work?
I have seen many examples involving type classes and implicit conversion, but none so far operating on generic types.
implicit def myListToStreamImpl[T]: MyListExt[T, MyList[T]] = new MyListExt[T, MyList[T]] {
override def stream(a: MyList[T]): Stream[T] = {
def fold(l: MyList[T1], acc: Stream[T1]): Stream[T1] = l match {
case MyList.Empty() => acc
case MyList.Node(value, next) => fold(next, acc :+ value)
}
fold(a, Stream.empty[T1])
}
}
Your types don't align because you've used that private type for whatever bizarre reason. Types nested inside objects have a completely different application, and they don't relate to your current use case.
The trouble is that in l.stream.foreach(println) the l is implicitly transformed to new MyListExt.ops.MyListExtOps[.., ..](l) and generics are inferred to be [Nothing, MyList[Int]], which doesn't satisfy [T, A <: MyList[T]].
I can't see reason to parametrize MyListExt with both T and A <: MyList[T]. I guess T is enough, use MyList[T] instead of A.
Don't use private type T0, just parametrize myListToStreamImpl (make it def) with T0 aka T.
Try
trait MyList[A]
object MyList {
case class Empty[A]() extends MyList[A]
case class Node[A](value: A, next: MyList[A]) extends MyList[A]
}
trait MyListExt[T] {
def stream(a: MyList[T]): Stream[T]
}
object MyListExt {
def apply[T](implicit a: MyListExt[T]): MyListExt[T] = a
object ops {
implicit class MyListExtOps[T](val a: MyList[T]) extends AnyVal {
def stream(implicit ext: MyListExt[T]): Stream[T] = ext.stream(a)
}
}
implicit def myListToStreamImpl[T]: MyListExt[T] = new MyListExt[T] {
override def stream(a: MyList[T]): Stream[T] = {
def fold[T1](l: MyList[T1], acc: Stream[T1]): Stream[T1] = l match {
case MyList.Empty() => acc
case MyList.Node(value, next) => fold(next, acc :+ value)
}
fold(a, Stream.empty)
}
}
}
object MyListTest {
def main(args: Array[String]): Unit = {
import MyListExt.ops._
val l: MyList[Int] = MyList.Node(1, MyList.Node(2, MyList.Node(3, MyList.Empty())))
l.stream.foreach(println)
}
}
Is it possible to somehow extend the solution to a sum type?
sealed trait Group
case class A extends Group
case class B extends Group
case class C extends Group
def divide(l : List[Group]): //Something from what I can extract List[A], List[B] and List[C]
May be you can try improving this answer. This may not solve your problem as it is difficult to know the the arbitrary subtypes of a given type (Group type might have any number of subtypes). In the case of Either it is easy to predict it's subtype as Right or Left.
sealed trait Group
case class A(name:String) extends Group
case class B(name:String) extends Group
case class C(name:String) extends Group
val list = List(
A("a1"), A("a2"), A("a3"), A("a4"),
B("b1"), B("b2"), B("b3"), B("b4"),
C("c1"), C("c2"), C("c3"), C("c4")
)
def divide(
list: List[Group],
aList : List[A],
bList: List[B],
cList: List[C]
): (List[A], List[B], List[C]) = {
list match {
case Nil => (aList, bList, cList)
case head :: tail => head match {
case a : A => divide(tail, aList.:+(a), bList, cList)
case b : B => divide(tail,aList, bList.:+(b), cList)
case c : C => divide(tail, aList, bList, cList.:+(c))
}
}
}
divide(list, List.empty[A], List.empty[B], List.empty[C])
//res1: (List[A], List[B], List[C]) = (List(A(a1), A(a2), A(a3), A(a4)),List(B(b1), B(b2), B(b3), B(b4)),List(C(c1), C(c2), C(c3), C(c4)))
Hope this helps you.
Have done it myself using Shapeless:
import shapeless.{:+:, ::, CNil, Coproduct, Generic, HList, HNil}
/*
Suppose we have a sealed trait and few implementations:
sealed trait Animal
case class Cat(a: Int) extends Animal
case class Dog(b: Int) extends Animal
case class Fox(c: Int) extends Animal
and a list:
val animals: List[Animal]
how to split the list into sub-lists per a subclass?
val cats: List[Cat] = ???
val dogs: List[Dog] = ???
val foxes: List[Fox] = ???
Of course it must work w/o boilerplate for arbitrary numbers of children
*/
object Split {
trait Splitter[T <: Coproduct] {
type R <: HList
def split(list: List[T]): R
}
type Aux[T <: Coproduct, R0 <: HList] = Splitter[T] {
type R = R0
}
implicit val cNilSplitter = new Splitter[CNil] {
type R = HNil
override def split(list: List[CNil]): HNil = HNil
}
implicit def cPllusSplitter[H, T <: Coproduct, R <: HList](implicit ev: Aux[T, R]): Aux[H :+: T, List[H] :: ev.R] = new Splitter[H :+: T] {
type R = List[H] :: ev.R
override def split(list: List[H :+: T]): ::[List[H], ev.R] = {
val heads: List[H] = list.flatMap(e => e.eliminate(h => Some(h), t => None))
val tails: List[T] = list.flatMap(e => e.eliminate(h => None, t => Some(t)))
val sub: ev.R = ev.split(tails)
heads :: sub
}
}
def splitCoproduct[T <: Coproduct, R <: HList](list: List[T])(implicit ev: Aux[T, R]): R = ev.split(list)
def split[X, T <: Coproduct, R <: HList](list: List[X])(implicit gen: Generic.Aux[X, T], ev: Aux[T, R]): R = {
val asCoproduct: List[T] = list.map(gen.to)
splitCoproduct[T, R](asCoproduct)(ev)
}
}
object Runner {
import Split._
def main(args: Array[String]): Unit = {
sealed trait Animal
case class Cat(a: Int) extends Animal
case class Dog(b: Int) extends Animal
case class Fox(c: Int) extends Animal
val animals: List[Animal] = List(Cat(1), Dog(1), Cat(2), Fox(1), Dog(2), Dog(3))
val result = split(animals) //List[Cat] :: List[Dog] :: List[Fox] :: HNil
println(result)
val cats: List[Cat] = result.head
val dogs: List[Dog] = result.tail.head
val foxes: List[Fox] = result.tail.tail.head
println(cats)
println(dogs)
println(foxes)
}
}
Say I have a 'wide' sealed class hierarchy:
sealed trait Alphabet
case class A(word: String) extends Alphabet
...
case class Z(word: String) extends Alphabet
And say I have a type class instance defined for each child class in the hierarchy:
trait SwearWordFinder[T <: Alphabet] {
def isSwearWord(x: T): Boolean
}
val swearWordFinderA = new SwearWordFinder[A] { ... }
...
val swearWordFinderZ = new SwearWordFinder[Z] { ... }
Is there a way I can define a type class instance for the Alphabet trait itself without having to implement it by pattern matching (as below)?
def isSwearWord(x: Alphabet): Boolean = x match {
case a: A => swearWordFinderA.isSwearWord(a)
...
case z: Z => swearWordFinderZ.isSwearWord(z)
}
You can represent Alphabet as a Shapeless Coproduct of A :+: B :+: ... :+: Z :+: CNil, so if you have SwearWordFinder instances for A, B, ... and define instances for CNil and :+: you can get a SwearWordFinder[Alphabet] using its generic representation.
import shapeless._
trait SwearWordFinder[T] {
def isSwearWord(x: T): Boolean
}
object SwearWordFinder extends SwearWordFinder0 {
implicit def apply[T](implicit swf: SwearWordFinder[T]): SwearWordFinder[T] = swf
implicit val cnilSwearWordFinder: SwearWordFinder[CNil] =
new SwearWordFinder[CNil] {
def isSwearWord(t: CNil): Boolean = false
}
implicit def coproductConsSwearWordFinder[L, R <: Coproduct](implicit
lSwf: SwearWordFinder[L],
rSwf: SwearWordFinder[R]
): SwearWordFinder[L :+: R] =
new SwearWordFinder[L :+: R] {
def isSwearWord(t: L :+: R): Boolean =
t.eliminate(lSwf.isSwearWord, rSwf.isSwearWord)
}
}
trait SwearWordFinder0 {
implicit def genericSwearWordFinder[T, G](implicit
gen: Generic.Aux[T, G],
swf: Lazy[SwearWordFinder[G]]
): SwearWordFinder[T] =
new SwearWordFinder[T] {
def isSwearWord(t: T): Boolean = swf.value.isSwearWord(gen.to(t))
}
}
Now some instances for our letters :
sealed trait Alphabet extends Product with Serializable
object Alphabet {
final case class A(word: String) extends Alphabet
final case class Z(word: String) extends Alphabet
}
implicit val swfA = new SwearWordFinder[Alphabet.A] {
def isSwearWord(a: Alphabet.A) = a.word == "apple"
}
implicit val swfZ = new SwearWordFinder[Alphabet.Z] {
def isSwearWord(z: Alphabet.Z) = z.word == "zebra"
}
And now we can get a SwearWordFinder[Alphabet] :
def isBadWord[T](t: T)(implicit swfT: SwearWordFinder[T]): Boolean =
swfT.isSwearWord(t)
val a1: Alphabet = Alphabet.A("apple")
val z2: Alphabet = Alphabet.Z("zorro")
val z3: Alphabet = Alphabet.Z("zebra")
isBadWord(a1) // true
isBadWord(z2) // false
isBadWord(z3) // true
Like I mentioned in my comment: beware of SI-7046. Your Alphabet AST needs to be in a project on which the project with SwearWordFinder depends or in a package which will be compiled before the package with the type class derivation for Alphabet.
I cannot make this work:
abstract class AlternativeSortingType[T, B](implicit val ord: scala.math.Ordering[B]) {
def convert(t: T): B
}
case class LengthSortingType() extends AlternativeSortingType[String, Int] {
def convert(t: String): Int = t.length
}
class ProduceResult {
var c: AlternativeSortingType[String, _] = LengthSortingType()
def sort(l: List[String]) = l.sortBy(c.convert(_))(c.ord)
}
It complains with the following:
<console>:20: error: type mismatch;
found : scala.math.Ordering[_$1]
required: scala.math.Ordering[Any]
Note: _$1 <: Any, but trait Ordering is invariant in type T.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
def sort(l: List[String]) = l.sortBy(c.convert(_))(c.ord)
^
I already tried to put the type B to the method only, but I don't see how to get rid of the wilcard. I want to be able to sort by specifying any kind of converter.
How to make the type match?
I found a workaround in which I can even now make composition of orderings.
abstract class AlternativeSortingType[T] extends Ordering[T] { self =>
def &&(other: AlternativeSortingType[T]): AlternativeSortingType[T] = new AlternativeSortingType[T] {
def compare(e: T, f: T): Int = {
val ce = self.compare(e, f)
if(ce == 0) other.compare(e, f) else ce
}
}
}
case class LengthSortingType() extends AlternativeSortingType[String] {
def compare(e: String, f: String): Int = e.length - f.length
}
case class DictionarySortingType() extends AlternativeSortingType[String] {
def compare(e: String, f: String): Int = if(e < f) -1 else if(e==f) 0 else 1
}
class ProduceResult {
var c: AlternativeSortingType[String] = LengthSortingType() && DictionarySortingType()
def sort(l: List[String]) = l.sortWith((e,f) => c.compare(e, f) < 0)
}