Стеки Monad Transformer в Scala

Я изучаю преобразователи монад в Scala, но столкнулся с проблемой, которую пока не могу решить. В моем стеке преобразователя монад я составляю монаду Either и State. Однако мне не удается вызвать функции, принадлежащие одной из двух монад:

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

Сбой со следующим сообщением об ошибке:

[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]

Если я изменю стек преобразователя монад на:

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

Программа компилируется и запускается без проблем. Кто-нибудь знает, что я здесь делаю не так?


person user3459840    schedule 25.03.2014    source источник


Ответы (2)


Вызов метода get[Int] возвращает IndexedStateT[Id, Int, Int, Int]. Ваш Stack[Int] заменяется на IndexedStateT[Inner, Int, Int, Int], где Inner - это EitherT[Id, String, A]. Об этом немного сложно рассуждать, поэтому я немного упрощу ваш пример.

Вместо псевдонима типа Inner мы создаем StateT с Option.

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

Присвоение get[Int] все равно не удастся.

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]

Чтобы решить эту проблему, нам нужно lift преобразователь в Option:

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

Если вы переведете это в свой пример кода, вам нужно будет lift State на Inner, как это. Обратите внимание, что вам также необходимо изменить определение Inner, чтобы оно было ковариантным:

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

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

Чтобы написать это, не поднимая вручную, вы можете ввести неявное преобразование. Полный пример:

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
}
person EECOLOR    schedule 25.03.2014

Я начал писать это как комментарий к ответу EECOLOR (за который я только что проголосовал и который я рекомендую - кроме из неявного преобразования в конце), но он стал немного громоздким, так что вот новый ответ.

Диагноз EECOLOR совершенно правильный, но MonadState (который я использовал в моем ответе на другой ваш вопрос сегодня утром) позволяет избежать явный подъем. Например, вы можете написать следующее:

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

Обратите внимание, что (как и в предыдущем вопросе) я изменил Stack, чтобы он был параметризован по типу состояния. Вы можете легко изменить это на что-то вроде этого:

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

Если вы хотите зафиксировать тип состояния в стеке.

person Travis Brown    schedule 25.03.2014
comment
Я не знал о MonadState. Это потрясающе! Спасибо, что поделился. - person Golly; 08.11.2014