【问题标题】:Why Free is not monad instance in Scalaz 7.1.5?为什么 Free 在 Scalaz 7.1.5 中不是 monad 实例?
【发布时间】:2015-11-19 00:08:05
【问题描述】:

由于Free 不是Scalaz 7.1.5 中的monad 实例,我不能使用ApplicativeApply 等中定义的有用方法。

/* 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

错误信息是,

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
              ^

我应该为 Free 实现 monad 实例吗?

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)
}

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

【问题讨论】:

    标签: scala monads scalaz


    【解决方案1】:

    这只是 Scala 编译器对类型别名的挑剔。您有两个选择(或至少有两个选择——可能还有其他合理的解决方法)。首先是稍微不同地分解类型别名。而不是这个:

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

    你写这个:

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

    然后Monad[ResultSetIO] 将编译得很好。您将需要为 |@|*>replicateM 额外导入一次:

    import scalaz.syntax.applicative._
    

    另一种选择是保留 FreeC 原样并自己定义 monad 实例,因为 scalac 不会为您找到它。幸运的是,您可以比按照您的建议写出来更简单一些:

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

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

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

    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)
    

    这将在 7.1.5 中正常编译。


    为了完整起见,还有第三种方法,即定义一些 Unapply 机制来帮助编译器找到 FreeC 版本的实例(Rob Norris 是 responsible 这段代码,我已经只是去类投影):

    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
    }
    

    这允许您使用FreeC 而无需每次都定义monad 实例。不过,我仍然认为放弃FreeC 并使用Free 是一个更好的主意。

    【讨论】:

    • 哦,还有一个:4. 支持 SI-5075,交叉手指,再等四五年……
    • 感谢您的详细解释和其他有用的替代方案。我想选择第一种方式,因为 Scalaz 7.2.x 没有FreeC
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    • 1970-01-01
    • 2017-03-25
    • 2016-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多