5

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.

Archeg
  • 7,785
  • 5
  • 34
  • 81

1 Answers1

4

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.

Community
  • 1
  • 1
Travis Brown
  • 135,682
  • 12
  • 352
  • 654
  • I've seen patterns like `val emptyTree: Tree[Nothing] ..` in scalaz sources, but couldn't understand why it was done like that. Now it is clear to me. There is still something I do not understand, is there any particular reason why in scalaz `ShowOps` was not defined like I did? It seems simpler than having to define wrapper methods like `emptyTree` and `node[T]` – Archeg Jan 20 '16 at 18:47
  • The up-casting constructor pattern solves many of these issues across the board in one place, while providing instances for subtypes would have an impact on tons of code. In general Scalaz doesn't go out of its way to accommodate subtyping (e.g. most type classes are invariant and instances aren't provided for subtypes). – Travis Brown Jan 20 '16 at 18:53