为什么Free不是Scalaz 7.1.5中的monad实例?

1am*_*bda 5 monads scala scalaz

因为Free在Scalaz 7.1.5中不是monad实例,所以我不能使用定义的有用方法Applicative,Apply依此类推.

/* ref - http://tpolecat.github.io/assets/sbtb-slides.pdf */
import Free._, Coyoneda._

type ResultSetIO[A] = FreeC[ResultSetOp, A]

val next                 : ResultSetIO[Boolean] = liftFC(Next)
def getString(index: Int): ResultSetIO[String]  = liftFC(GetString(index))
def getInt(index: Int)   : ResultSetIO[Int]     = liftFC(GetInt(index))
def close                : ResultSetIO[Unit]    = liftFC(Close) 

// compile errors
def getPerson1: ResultSetIO[Person] =
  (getString(1) |@| getInt(2)) { Person(_, _)}

def getNextPerson: ResultSetIO[Person] =
  next *> getPerson

def getPeople(n: Int): ResultSetIO[List[Person]] =
  getNextPerson.replicateM(n) // List.fill(n)(getNextPerson).sequence
Run Code Online (Sandbox Code Playgroud)

erorr信息是,

Error:(88, 19) value |@| is not a member of free.JDBC.ResultSetIO[String]
(getString(1) |@| getInt(2)) { Person(_, _)}
              ^
Error:(91, 10) value *> is not a member of free.JDBC.ResultSetIO[Boolean]
next *> getPerson
     ^
Error:(94, 19) value replicateM is not a member of free.JDBC.ResultSetIO[free.Person]
getNextPerson.replicateM(n) // List.fill(n)(getNextPerson).sequence
              ^
Run Code Online (Sandbox Code Playgroud)

我应该实现monad实例Free吗?

implicit val resultSetIOMonadInstance = new Monad[ResultSetIO] {
  override def bind[A, B](fa: ResultSetIO[A])(f: (A) => ResultSetIO[B]): ResultSetIO[B] =
    fa.flatMap(f)

  override def point[A](a: => A): ResultSetIO[A] =
    Free.point[CoyonedaF[ResultSetOp]#A, A](a)
}
Run Code Online (Sandbox Code Playgroud)

或者,我错过了什么?(例如进口)

Tra*_*own 6

这只是Scala编译器对类型别名的挑剔.您有两个选择(或至少两个选择 - 可能还有其他合理的解决方法).第一种是略微区分类型别名.而不是这个:

type ResultSetIO[A] = FreeC[ResultSetOp, A]
Run Code Online (Sandbox Code Playgroud)

你写这个:

type CoyonedaResultSetOp[A] = Coyoneda[ResultSetOp, A]
type ResultSetIO[A] = Free[CoyonedaResultSetOp, A]
Run Code Online (Sandbox Code Playgroud)

然后Monad[ResultSetIO]编译就好了.您将需要一个额外的进口|@|,*>以及replicateM:

import scalaz.syntax.applicative._
Run Code Online (Sandbox Code Playgroud)

另一种选择是保持FreeC原样并自己定义monad实例,因为scalac不会为你找到它.幸运的是,你可以做到这一点,而不是像你提议的那样写出来:

implicit val monadResultSetIO: Monad[ResultSetIO] =
  Free.freeMonad[({ type L[x] = Coyoneda[ResultSetOp, x] })#L]
Run Code Online (Sandbox Code Playgroud)

我更喜欢第一种方法,但你选择哪种方法并不重要.

为方便起见,这是一个简化的完整工作示例:

sealed trait ResultSetOp[A]
case object Next extends ResultSetOp[Boolean]
case class GetString(index: Int) extends ResultSetOp[String]
case class GetInt(index: Int) extends ResultSetOp[Int]
case object Close extends ResultSetOp[Unit]

import scalaz.{ Free, Coyoneda, Monad }
import scalaz.syntax.applicative._

type CoyonedaResultSetOp[A] = Coyoneda[ResultSetOp, A]
type ResultSetIO[A] = Free[CoyonedaResultSetOp, A]

val next: ResultSetIO[Boolean] = Free.liftFC(Next)
def getString(index: Int): ResultSetIO[String] = Free.liftFC(GetString(index))
def getInt(index: Int): ResultSetIO[Int] = Free.liftFC(GetInt(index))
def close: ResultSetIO[Unit] = Free.liftFC(Close)

case class Person(s: String, i: Int)

def getPerson: ResultSetIO[Person] = (getString(1) |@| getInt(2))(Person(_, _))
def getNextPerson: ResultSetIO[Person] = next *> getPerson
def getPeople(n: Int): ResultSetIO[List[Person]] = getNextPerson.replicateM(n)
Run Code Online (Sandbox Code Playgroud)

这将用7.1.5编译得很好.


为了完整起见,还有第三种方法,即定义一些Unapply机制来帮助编译器找到FreeC版本的实例(Rob Norris 负责这个代码,我刚刚对它进行了类型预测):

implicit def freeMonadC[FT[_[_], _], F[_]](implicit
  ev: Functor[({ type L[x] = FT[F, x] })#L]
) = Free.freeMonad[({ type L[x] = FT[F, x] })#L]

implicit def unapplyMMFA[TC[_[_]], M0[_[_], _], M1[_[_], _], F0[_], A0](implicit
  TC0: TC[({ type L[x] = M0[({ type L[x] = M1[F0, x] })#L, x] })#L]
): Unapply[TC, M0[({ type L[x] = M1[F0, x] })#L, A0]] {
  type M[X] = M0[({ type L[x] = M1[F0, x] })#L, X]
  type A = A0
} = new Unapply[TC, M0[({ type L[x] = M1[F0, x] })#L, A0]] {
  type M[X] = M0[({ type L[x] = M1[F0, x] })#L, X]
  type A = A0
  def TC = TC0
  def leibniz = Leibniz.refl
}
Run Code Online (Sandbox Code Playgroud)

这允许您在FreeC不使用每次定义monad实例的情况下使用.不过,我仍然认为放弃FreeC和使用Free是一个更好的主意.