Reputation: 125
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?
Upvotes: 4
Views: 1337
Reputation: 139038
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.
Upvotes: 5
Reputation: 11244
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
}
Upvotes: 5