我希望能够定义一个代数类型并在其上定义一个简单的类型类,让我们说Show.在哈斯克尔我做:
data Tree a = EmptyTree | Node a deriving (Show)
现在,如果我键入EmptyTree – haskell可以显示它,所以它属于Show.
现在我想在scala中做同样的事情:
sealed abstract class Tree[+T] case object EmptyTree extends Tree[Nothing] case class Node[T](value: T) extends Tree[T]
然后我定义围绕它显示:
implicit def show[T] = Show.showA[Tree[T]]
我可以做println((EmptyTree:Tree [Int]).show).但我不能做println(EmptyTree.show)(响应是值显示不是对象EmptyTree的成员)
我必须写更多:
implicit class MyShowOps[A, +T <: Tree[A]](t: T) { def showMy(implicit ev: Show[Tree[A]]): String = ev.shows(t) }
只有这样我才能做println(EmptyTree.showMy)
它仍然听起来不正确,我相信我要么做错了,我不应该像这样应用Show而应该只使用我的构造作为Tree [T]或者我错过了Scalaz的正确构造.
Scala对ADT的表示与Haskell的不同之处在于它的构造函数有自己的类型.这部分是关于实际的互操作性 – 在JVM上使用子类型是很自然的 – 它有 both advantages and disadvantages.您遇到了一个缺点,即将值静态类型化为构造函数类型通常会使类型推断和隐式解析变得复杂.
类型类实例是静态解析的,在您的情况下,Show不是逆变的,因此Tree [T]的实例不是EmptyTree.type的实例.从Scalaz角度来看,最惯用的解决方案是提供返回ADT类型的智能构造函数:
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]] }
现在您可以编写Tree.emptyTree.show.
请注意,即使是更简单的上下文,这个问题也会出现.例如,假设我们想要使用Option作为累加器来折叠列表:
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)) ^
因为Some(0)的推断类型是Some [Int],而不是Option [Int],所以为foldLeft方法推断的类型参数对于地图的结果限制太多.
如果标准库为这样的情况提供Option.none和Option.some“构造函数”会很好,但事实并非如此,所以你要么必须在第一个参数上放置一个类型注释,要么使用像Scalaz的none和一些:
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)
在您的情况下,您可能会控制ADT定义,因此您可以自己提供这样的智能构造函数.