문제

I'm learning about monad transformers in Scala but I ran into a problem which I find impossible to solve so far. In my monad transformer stack I compose the Either and the State monad. However, I fail to call functions belonging to one of the two monads:

import scalaz._
import Scalaz._

object Minimal {
  type Inner[A] = EitherT[Id, String, A]
  type Outer[F[+_], A] = StateT[F,Int,A]
  type Stack[A] = Outer[Inner, A]

  def foo:Stack[Int] = for {
    n <- get[Int]
  } yield {
    2 * n
  }

  def main(args: Array[String]): Unit = {
    val x = foo.eval(8)
    println(x)
  }
}

Fails with the following error message:

[error] Minimal.scala:10: type mismatch;
[error]  found   : scalaz.IndexedStateT[scalaz.Id.Id,Int,Int,Int]
[error]  required: Minimal.Stack[Int]
[error]     (which expands to)  scalaz.IndexedStateT[Minimal.Inner,Int,Int,Int]
[error]     n <- get[Int]

If I change the monad transformer stack to:

type Stack[A] = State[Int,A]

The program compiles and runs without a problem. Does anybody knows what I do wrong here?

도움이 되었습니까?

해결책

The method call get[Int] returns an IndexedStateT[Id, Int, Int, Int]. Your Stack[Int] expands to IndexedStateT[Inner, Int, Int, Int] where Inner is an EitherT[Id, String, A]. This is a bit hard to reason about, so I will simplify your example a bit.

Instead of the Inner type alias we create a StateT with an Option.

type Stack[A] = StateT[Option, Int, A]

The assignment of get[Int] will still fail.

val x:Stack[Int] = get[Int]
//type mismatch; 
//  found : scalaz.State[Int,Int]
//    (which expands to) scalaz.IndexedStateT[scalaz.Id.Id,Int,Int,Int]
//  required: Minimal.Stack[Int] 
//    (which expands to) scalaz.IndexedStateT[Option,Int,Int,Int]

In order to fix this problem we need to lift the transformer to an Option:

val x:Stack[Int] = get[Int].lift[Option]

If you translate that to your example code, you would need to lift the State to an Inner like this. Note that you need to change your definition of Inner to be covariant as well:

type Inner[+A] = EitherT[Id, String, A]
type Stack[A] = StateT[Inner, Int, A]

val x:Stack[Int] = get[Int].lift[Inner]

To be able to write this without lifting manually you could introduce an implicit conversion. The full example:

type Inner[+A] = EitherT[Id, String, A]
type Outer[F[+_], A] = StateT[F, Int, A]
type Stack[A] = Outer[Inner, A]

implicit def liftToStack[A](x:Outer[Id, A]):Stack[A] = x.lift[Inner]

def foo: Stack[Int] = for {
  n <- get[Int]
} yield {
  2 * n
}

다른 팁

I started writing this as a comment on EECOLOR's answer (which I've just upvoted, and which I recommend—apart from the implicit conversion at the end), but it got a little unwieldy, so here's a new answer.

EECOLOR's diagnosis is exactly right, but MonadState (which I used in my answer to your other question this morning) lets you avoid the explicit lifting. For example, you could write the following:

import scalaz._, Scalaz._

type Inner[+A] = EitherT[Id, String, A]
type Stack[S, +A] = StateT[Inner, S, A]

def foo: Stack[Int, Int] = for {
  n <- MonadState[Stack, Int].get
} yield 2 * n

Note that (as in my earlier question) I've changed Stack to be parametrized on the state type. You could easily change this to something like this:

type MyState[S, +A] = StateT[Inner, S, A]
type Stack[+A] = MyState[Int, A]

If you wanted to capture the state type in the stack.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top