Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typeclasses and inheritance in scalaz

Tags:

scala

scalaz

This is my second try to define the problem, I can't get my head around it.

I want to be able to define an algebraic type and define a simple typeclass over it, let's say Show. In haskell I do:

data Tree a = EmptyTree | Node a deriving (Show)

Now, if I type EmptyTree - haskell can show it, so it belongs to Show.

Now I am trying to do the same in scala:

sealed abstract class Tree[+T]
case object EmptyTree extends Tree[Nothing]
case class Node[T](value: T) extends Tree[T]

Then I define Show around it:

implicit def show[T] = Show.showA[Tree[T]]

I can do println((EmptyTree : Tree[Int]).show). But I can't do println(EmptyTree.show) (response is value show is not a member of object EmptyTree)

I have to write additional:

implicit class MyShowOps[A, +T <: Tree[A]](t: T) {
  def showMy(implicit ev: Show[Tree[A]]): String = ev.shows(t)
}

And only then I can do println(EmptyTree.showMy)

It still doesn't sound correct, I believe either I am trying to do a wrong thing and I am not supposed to apply Show like that and should use my construction only as Tree[T] or I am missing a proper construction from Scalaz.

like image 876
Archeg Avatar asked Sep 25 '22 15:09

Archeg


1 Answers

Scala's representation of ADTs differs from Haskell's in that its constructors have their own types. This is partly about practical interoperability—using subtyping is natural on the JVM—and it has both advantages and disadvantages.

You're running into one of the disadvantages, which is that having values that are statically typed as a constructor type often complicates type inference and implicit resolution.

Type class instances are statically resolved, and in your case specifically Show isn't contravariant, so an instance for Tree[T] isn't an instance for EmptyTree.type. The most idiomatic solution from the Scalaz perspective is to provide smart constructors that return the ADT type:

import scalaz.Show, scalaz.syntax.show._

sealed abstract class Tree[+T]

object Tree {
  private[this] case object EmptyTree extends Tree[Nothing]
  private[this] case class Node[T](value: T) extends Tree[T]

  val emptyTree: Tree[Nothing] = EmptyTree
  def node[T](value: T): Tree[T] = Node(value)

  implicit def show[T]: Show[Tree[T]] = Show.showA[Tree[T]]
}

Now you can write Tree.emptyTree.show.

Note that this problem also turns up in even simpler contexts. For example, suppose we want to fold over a list with an Option as the accumulator:

scala> List(1, 2, 3).foldLeft(Some(0))((acc, i) => acc.map(_ + i))
<console>:11: error: type mismatch;
 found   : Option[Int]
 required: Some[Int]
       List(1, 2, 3).foldLeft(Some(0))((acc, i) => acc.map(_ + i))
                                                          ^

Because the inferred type for Some(0) is Some[Int], not Option[Int], the type parameter that's inferred for the foldLeft method is too restrictive for the result of the map.

It would be nice if the standard library provided Option.none and Option.some "constructors" for cases like this, but it doesn't, so you either have to put a type annotation on the first argument or use something like Scalaz's none and some:

scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> List(1, 2, 3).foldLeft(some(0))((acc, i) => acc.map(_ + i))
res0: Option[Int] = Some(6)

In your case you presumably control the ADT definition, so you can provide smart constructors like this yourself.

like image 71
Travis Brown Avatar answered Oct 11 '22 18:10

Travis Brown