如何在Scala中编写Haskell-do-notation

4

我用do-notation编写了以下Haskell代码。

我想将其转换为Scala代码。

main :: IO ()
main = do
  print $ func1 (Right  1) (Right 2)
  print $ func1 (Right 10) (Right 3)


func1 :: Either String Int -> Either String Int -> Either String Double
func1 e1 e2 = do
  v1 <- e1
  v2 <- e2
  if v1 < v2
    then Right 1.515151  -- No meaning
    else Left  "some error"

这里是Haskell的输出结果

Right 1.515151
Left "some error"

我写了以下Scala代码。但是当我看到 result <- if(v1 < v2)...yield result 时感到奇怪。

object Main {
  def main(args: Array[String]): Unit = {
    println(func1(Right(1))(Right(2)))
    println(func1(Right(10))(Right(3)))
  }

  def func1(e1: Either[String, Int])(e2: Either[String, Int]): Either[String, Double] =
    for{
      v1 <- e1
      v2 <- e2

      // Weird...
      result <- if(v1 < v2)
                  Right(1.515151)
                else
                  Left("some error")
    } yield result
}

以下是 Scala 的输出结果:

Right(1.515151)
Left(some error)

我想写下面的代码,但Scala不允许我这样写。
  // Invalid Scala Code
  def func1(e1: Either[String, Int])(e2: Either[String, Int]): Either[String, Double] =
    for{
      v1 <- e1
      v2 <- e2
    } {
      if(v1 < v2)
          Right(1.515151)
        else
          Left("some error")
    }

你能告诉我如何以优美的方式撰写文章吗?

2个回答

5

它可以进行美化。

for {
  v1  <- e1
  v2  <- e2
  res <- Either.cond(v1 < v2, 1.515151, "some error")
} yield res

最好加入一个守卫条件,但是根据Scala文档,不支持这样做,因为Either没有withFilter方法。


非常感谢您,jwvh!我不知道 Either.cond。实际上,我想使用 scalaz 中的 EitherT 而不是 Either,所以我搜索了 EitherT 中的 Either.cond 替代方法,但我找不到。您能告诉我是否知道吗? - ryo
抱歉,不了解scalaz。它在我的TODO列表上。 - jwvh

2
(免责声明:我不懂Haskell,所以我的翻译可能有误)
Haskell的do表示法和Scala的for/yield推导式的区别在于,do序列以一个绑定(即flatMap)结束,而for/yield以正常的map结束。
因此,在Haskell中,如果最后一步是一个纯值,你必须将其包装在return中,但在Scala中,你可以直接yield它。yield是一个关键字,不像Haskell的return是一个函数。另一方面,当最后一步是一个单子值时,在Haskell中你可以直接放置它,但在Scala中你必须添加一步使用result <- monadicValue,然后yield result。
这只是这两种语言设计上的差异,我相信你只需要习惯Scala如何处理这个问题。
至于你在其他答案的评论中提出的问题:
在scalaz中,你可以使用p.either(a).or(b)代替Either.cond(p, a, b),它返回一个disjunction:
scala> import scalaz._, Scalaz._

scala> true.either(10).or("error")
res0: scalaz.\/[String,Int] = \/-(10)

然后您可以将这个析取包装在您想要的单子中,再将其放在 EitherT 里面。例如:

scala> EitherT(true.either(10).or("error").some)
res1: scalaz.EitherT[Option,String,Int] = EitherT(Some(\/-(10)))

非常感谢Kolmar~ p.either(a).or(b) 是我想要的。谢谢!
我相信你只需要逐渐习惯Scala处理这个的方式。我想要做到。
- ryo
match-case版本吗?我的代码的最后一个表达式是if,但是在scalaz中是否有任何match-case版本? - ryo
match-case 版本是指 v1 < v2 match {case true => Right(1.515151); case false => Left("some error")}。(此示例可用 if 替换,但这只是一个示例。) - ryo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接