我正在使用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 \/""")
}
}
我不相信这是一个好主意,但它正在解决问题。
答案 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 answer到my 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)。