8

我正在使用 Scalaz 7 的 EitherT 来构建融合 State 和 \/. 到目前为止,一切都很好; 我得到的东西基本上是:

State[MyStateType, MyLeftType \/ MyRightType]

这使我可以构建在 <-.

但我不知道如何从状态动作中返回元组。单一的结果就好了——在下面的代码中,“val理解”正是我想要发生的。

但是当我想返回一个元组时,事情就崩溃了;“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 是一个单子的证据,即使它不是。在我的真实代码中,MyLeftType 是一个案例类(称为 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 \/""")
      }
  }

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

4

2 回答 2

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)

这不是很好,但可以完成工作。

(我真的很感激你为这个问题做了一个独立的、可行的例子。)

于 2013-07-02T08:52:57.617 回答
5

正如我在上面的评论中指出的那样,问题在于您的第二个理解的脱糖版本for涉及 2.10.2(和 2.10.1,但不是 2.10.0)中的过滤操作,并且无法过滤EitherT(或普通old \/) 左侧的类型没有幺半群实例。

在以下示例中很容易看出为什么需要幺半群:

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

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

assert(y == "".left)

根据我对 -comprehensions 中元组模式的相关问题的回答,您在 2.10.2 中看到的行为是正确且有意的——显然完全不必要的调用将继续存在。forwithFilter

您可以使用 Petr Pudlák 的答案中的解决方法,但还值得注意的是,以下无糖版本也非常清晰简洁:

val notAnotherComprehension = comprehension.flatMap {
  case (x, y) => doSomething.map((x, y, _))
}

无论如何,这或多或少是我天真地期望for-comprehension 去糖的原因(而且我不是唯一一个)。

于 2013-07-03T00:58:25.440 回答