Scalaz实例化类型类和案例类

时间:2016-06-10 08:17:02

标签: scala class oop functional-programming scalaz

这个问题很难解释。我尝试实现一种Arrow,将其称为MyArr,从Arrow扩展scalaz特征。但是,MyArr也是一种Algbraic数据类型,所以我用if创建了几个case类,并写下这样的代码(最小例子):

package org.timeless

import scalaz._
import Scalaz._
import std.option._, std.list._

sealed trait MyArr[A,B] extends Arrow[MyArr] {
  def id[A]: MyArr[A,A] = SId()
  def compose[A,B,C](s1: MyArr[B,C], s2: MyArr[A,B]): MyArr[A,C] =
    SRight(s2, s1)
  def arr[A,B](f: A=>B): MyArr[A,B] = SPure(f)
  def first[A,B,C](s: MyArr[A,B]): MyArr[(A,C),(B,C)] = SPar(s, SId())
}

case class SId[A] () extends MyArr[A,A]()
case class SRight[M[_],A,B,C](s1: MyArr[A,B], s2: MyArr[B,C]) extends MyArr[A,C]
case class SPure[A,B](f: A=>B) extends MyArr[A,B]
case class SPar[M[_],A,B,C,D](s1: MyArr[A,B], s2: MyArr[C,D]) extends MyArr[(A,C),(B,D)]

object MyArr {
  val b = SId[Int]()
  val c = SId[Int]()
  // val a: MyArr[Int,Int] = id[Int] // Error
  // val d = c compose b // Error
  // val d = b >>> c // Error
  val d = b >>> (b,c) // ??? Arguments
}

很容易看出,id函数实际上是SId等的构造函数。类本身编译正常,但我没有看到任何方法将它实际用作{{ {1}}。我实际上来自Haskell编程,所以我可能完全错了,但Haskell中的等效代码应该是这样的(使用GADT):

Arrow

1 个答案:

答案 0 :(得分:2)

Scala中的类型类

Scala中的Typeclasses并没有像Haskell那样内置于语言中。它们只是一种设计模式,通常在隐式参数的帮助下实现:

def pure[M[_], T](
  implicit monad: Monad[M] // Fetching a monad instance for type M
): M[T] = 
  monad.pure[T]            // Using methods of the monad instance

您不必扩展类型类特征,为某些ADT提供类型类实例。必须在某处将类型类实例定义为implicit valimplicit defimplicit object等。然后,您可以在使用之前将import相关实例放入范围。

还有一种方法可以自动提供类型类实例,而无需显式导入它。要做到这一点,实例应该在类型类class / trait或ADT class / trait的伴随对象中声明,或者在它们的父类/ trait之一中声明;或者在其中定义了其中一个的包对象中。您可以在此答案中阅读有关隐式解决方案主题的更多信息:Where does Scala look for implicits?

定义类型类实例

因此,要回到您的问题,您可以像这样定义您的ADT:

sealed trait MyArr[A,B]
case class SId[A] () extends MyArr[A,A]()
case class SRight[M[_],A,B,C](s1: MyArr[A,B], s2: MyArr[B,C]) extends MyArr[A,C]
case class SPure[A,B](f: A=>B) extends MyArr[A,B]
case class SPar[M[_],A,B,C,D](s1: MyArr[A,B], s2: MyArr[C,D]) extends MyArr[(A,C),(B,D)]

并在Arrow的伴随对象中提供MyArr实例:

object MyArr {
  implicit val arrow: Arrow[MyArr] = new Arrow[MyArr] {
    def id[A]: MyArr[A,A] = SId()
    def compose[A,B,C](s1: MyArr[B,C], s2: MyArr[A,B]): MyArr[A,C] =
    SRight(s2, s1)
    def arr[A,B](f: A=>B): MyArr[A,B] = SPure(f)
    def first[A,B,C](s: MyArr[A,B]): MyArr[(A,C),(B,C)] = SPar(s, SId())
  }

  def apply[T]: MyArr[T, T] = arrow.id[T]
}

使用类型类实例

由于Scala的某些限制,我认为在Scala中无法提供通用的id函数(或returnpure等)。类型推断。

相反,您可以使用上面定义的MyArr.apply方法:

val a: MyArr[Int,Int] = MyArr[Int]

由于ADT没有扩展类型类,因此它不会继承其compose>>>等方法。可以使用'Pimp My Library' pattern单独提供它们。

实际上,scalaz中的>>>方法来自ComposeOps类,其定义如下:

final class ComposeOps[F[_, _],A, B] private[syntax]
  (self: F[A, B])
  (val F: Compose[F]) 
  extends Ops[F[A, B]]

如果有一个F[_, _]实例可用于ComposeOps,则会从某个类型Compose定义到此类F的隐式转换:

implicit def ToComposeOps[F[_, _],A, B](v: F[A, B])(implicit F0: Compose[F])

此转换是使用import Scalaz._导入的,这就是为什么此行有效的原因:

val d = b >>> c

但是scalaz中没有扩展,它在带有compose实例的ADT上提供Arrow方法,所以这一行仍然会出错:

val e = c compose b

要使其正常工作,您可以自行提供扩展程序。一个简单的例子可能如下所示:

// implicit class is equivalent to a normal class 
// with an implicit conversion to this class
implicit class ArrowOps[T[_, _], A, B](f: T[A, B])(implicit arrow: Arrow[T]) {
  def compose[C](g: T[C, A]): T[C, B] = arrow.compose(f, g)
}
相关问题