2017-07-27 72 views
4

我使用do-nation編寫了以下Haskell代碼。如何在Scala中編寫Haskell-do-notation

,我想將它轉化成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" 

這裏是哈斯克爾

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 
} 

這裏是斯卡拉

Right(1.515151) 
Left(some error) 
輸出我想波紋管寫。但斯卡拉不允許我寫。

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

你能告訴我你用美麗的方式寫作的想法嗎?

回答

4

它可以被一些美化。

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

這將是很好,只是扔在監護條件,但根據Scala docs,這不是支持,因爲Either沒有withFilter方法。

+0

非常感謝你,jwvh!我不知道'two.cond'。其實我想在scalaz中使用'EitherT'而不是'Either',所以我搜索'EitherT'中的'Either.cond'選項,但是我找不到它。你能告訴我,如果你知道嗎? – redstone

+0

對不起,不知道斯卡拉。它在我的TODO清單上。 – jwvh

2

(聲明:我不知道哈斯克爾,所以我可能是錯與此)

Haskell的do符號之間的差異,以及Scala的for/yield理解的是,do序列與bind(即flatMap結束),但for/yield以正常map結束。

所以在Haskell中,如果你在最後一步有一個純粹的值,你必須將它包裝在return中,但是在Scala中你可以直接使用yieldyield是一個關鍵字,而不是像Haskell的return這樣的函數。另一方面,在最後一步中,您有一個單值,在Haskell中,您可以將它放在那裏,但在Scala中,您必須添加一個步驟result <- monadicValue,然後yield result

這只是這兩種語言在設計上的差異,我相信你必須習慣Scala如何做到這一點。


至於在其他答案的評論你的問題:

在scalaz代替Either.cond(p, a, b)可以使用p.either(a).or(b),它返回一個脫節:

scala> import scalaz._, Scalaz._ 

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

然後你可以用這在你想要的monad中進行析取,然後在EitherT。例如:

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

非常感謝Kolmar〜'p。(a)或者(b)'是我想要的。謝謝! >>我相信你只需要習慣Scala如何做到這一點。 我想要做的。 – redstone

+0

是否有'火柴盒'版本?我的代碼的最後一個表達式是'if',但scalaz中是否有'match-case'版本? – redstone

+0

'match-case' version意思是'v1 Right(1.515151); case false => Left(「some error」)}'。 (這個例子可以用'if'替換,但這只是一個例子。) – redstone