【问题标题】:How to return a tuple inside an EitherT如何在 EitherT 中返回元组
【发布时间】:2013-06-29 09:10:49
【问题描述】:

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

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 \/""")
      }
  }

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

【问题讨论】:

  • 2.10.1+ 对for-comprehension 进行解糖的方式有些奇怪——请参阅this question 了解同一问题的简化版本。
  • 并且要清楚,这是因为过滤EitherT(或\/)需要左侧的幺半群实例,并且由于某种原因2.10.2在此粘贴了过滤操作for-comprehension.

标签: scala scalaz monad-transformers scalaz7


【解决方案1】:

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

在下面的例子中很容易看出为什么幺半群是必要的:

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

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

assert(y == "".left)

根据this answermy related question 关于for-comprehensions 中的元组模式,您在2.10.2 中看到的行为是正确且有意的——显然完全不必要的withFilter 调用将继续存在.

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

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

这或多或少是我天真地期望for-comprehension 去糖的,无论如何(我是not the only one)。

【讨论】:

    【解决方案2】:

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

    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)
    

    这不是很好,但确实可以。

    (我非常感谢您为该问题提供了一个独立的、有效的示例。)

    【讨论】:

    • 而不是 x = p._1; y = p._2;你可以做(​​x,y)= p。感觉很奇怪,您需要在另一行执行此操作(@TravisBrown 的链接和评论有有用的信息)。
    猜你喜欢
    • 2014-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多