如何在EitherT中返回一个元组

时间:2013-07-02 05:43:33

标签: scala scalaz monad-transformers scalaz7

我正在使用Scalaz 7的EitherT来构造混合State和\ /的for-comprehension。到现在为止还挺好;我得到的东西基本上是:

State[MyStateType, MyLeftType \/ MyRightType]

这允许我在< - 。

的左侧构建具有漂亮变量的for-comprehension。

但我无法弄清楚如何从状态动作中返回元组。单个结果很好 - 在下面的代码中,“val comprehension”正是我想要发生的。

但是当我想要回归一个元组时,事情会崩溃; “val otherComprehension”不会让我做到

(a, b) <- comprehension

看起来它希望\ /的左侧是Monoid,我不明白为什么。我错过了什么?

(Scalaz 7 2.0.0-SNAPSHOT,Scala 2.10.2)

object StateProblem {
  case class MyStateType
  case class MyRightType
  case class MyLeftType

  type StateWithFixedStateType[+A] = State[MyStateType, A]
  type EitherTWithFailureType[F[+_], A] = EitherT[F, MyLeftType, A]
  type CombinedStateAndFailure[A] = EitherTWithFailureType[StateWithFixedStateType, A]

  def doSomething: CombinedStateAndFailure[MyRightType] = {
    val x = State[MyStateType, MyLeftType \/ MyRightType] {
      case s => (s, MyRightType().right)
    }
    EitherT[StateWithFixedStateType, MyLeftType, MyRightType](x)
  }

  val comprehension = for {
    a <- doSomething
    b <- doSomething
  } yield (a, b)

  val otherComprehension = for {
    // this gets a compile error:
    // could not find implicit value for parameter M: scalaz.Monoid[com.seattleglassware.StateProblem.MyLeftType]
    (x, y) <- comprehension

    z <- doSomething
  } yield (x, y, z)
}

编辑:我添加了MyLeftType是monad的证据,即使它不是。在我的实际代码中,MyLeftType是一个case类(称为EarlyReturn),所以我可以提供一个零,但只有在其中一个参数为零时才附加:

  implicit val partialMonoidForEarlyReturn = new Monoid[EarlyReturn] {
    case object NoOp extends EarlyReturn
    def zero = NoOp
    def append(a: EarlyReturn, b: => EarlyReturn) =
      (a, b) match {
        case (NoOp, b) => b
        case (a, NoOp) => a
        case _         => throw new RuntimeException("""this isnt really a Monoid, I just want to use it on the left side of a \/""")
      }
  }

我不相信这是一个好主意,但它正在解决问题。

2 个答案:

答案 0 :(得分:5)

在不知道原因的情况下,我找到了可能的解决方法:

for {
  //(x, y) <- comprehension
  p <- comprehension

  z <- doSomething
} yield (p._1, p._2, z)

或者可能稍好一点

for {
  //(x, y) <- comprehension
  p <- comprehension
  (x, y) = p

  z <- doSomething
} yield (x, y, z)

这不是很好,但能完成这项工作。

(我真的很感谢你为这个问题做了一个独立的,有效的例子。)

答案 1 :(得分:5)

正如我在上面的评论中所指出的那样,问题在于你的第二个for - 理解的desugared版本涉及2.10.2(和2.10.1,但不是2.10.0)中的过滤操作,以及如果没有左侧类型的monoid实例,则无法过滤EitherT(或普通旧\/)。

在以下示例中很容易理解为什么需要monoid:

val x: String \/ Int = 1.right
val y: String \/ Int = x.filter(_ < 0)

什么是y?很明显,它必须是某种“空”String \/ Int,并且由于\/是正确的,我们知道它不能是那方面的价值。所以我们左边需要一个零,而String的monoid实例提供了这个 - 它只是空字符串:

assert(y == "".left)

根据this answermy related question关于for中的元组模式 - 理解,你在2.10.2中看到的行为是正确的和有意的 - 显然是完全不必要的{{ {1}}留在这里。

您可以使用PetrPudlák的答案中的解决方法,但同样值得注意的是,以下无糖版本也非常简洁明了:

withFilter

这或多或少是我天真地期望val notAnotherComprehension = comprehension.flatMap { case (x, y) => doSomething.map((x, y, _)) } - 对desugar的理解,无论如何(我not the only one)。