Monad for-comprehensions with implicit Monad 失败,使用继承?

Monad for-comprehensions with implicit Monad fails, use inheritance?

我 运行 喜欢这张著名的 Scala 10 年前的票 https://github.com/scala/bug/issues/2823

因为我希望 for-comprehensions 像 Haskell 中的 do-blocks 一样工作。为什么他们不应该,Monads 与糖的一面搭配得很好。此时我有这样的东西:

import scalaz.{Monad, Traverse}
import scalaz.std.either._
import scalaz.std.list._

type ErrorM[A] = Either[String, A]
def toIntSafe(s : String) : ErrorM[Int] = {
   try {
      Right(s.toInt)
   } catch {
      case e: Exception => Left(e.getMessage)
   }
}

def convert(args: List[String])(implicit m: Monad[ErrorM], tr: Traverse[List]): ErrorM[List[Int]] = {
   val expanded = for {
      arg <- args
      result <- toIntSafe(arg)
   } yield result

   tr.sequence(expanded)(m)
}

println(convert(List("1", "2", "3")))
println(convert(List("1", "foo")))

我收到错误

"Value map is not a member of ErrorM[Int]" result <- toIntSafe(arg)

我如何回到我习惯的美丽的一元理解?一些研究表明 FilterMonadic[A, Repr] 摘要 class 是什么,如果你想成为一个理解,任何结合 FilterMonadic 和 scalaz 的例子?

  1. 我可以重复使用我的 Monad 隐式而不必重新定义 map、flatMap 等吗?

  2. 我可以保留我的类型别名而不必回绕吗?或者更糟,为 ErrorM 重新定义大小写 classes?

使用 Scala 2.11.8

编辑:我正在添加 Haskell 代码来证明这在没有显式 Monad 转换器的 GHC 中确实有效,只有 Either 和 List 的遍历和默认 monad 实例。

type ErrorM = Either String

toIntSafe :: Read a => String -> ErrorM a
toIntSafe s = case reads s of
                  [(val, "")] -> Right val
                  _           -> Left $ "Cannot convert to int " ++ s

convert :: [String] -> ErrorM [Int]
convert = sequence . conv
    where conv s = do
            arg <- s
            return . toIntSafe $ arg

main :: IO ()
main = do
    putStrLn . show . convert $ ["1", "2", "3"]
    putStrLn . show . convert $ ["1", "foo"]

你的 haskell 代码和你的 scala 代码不等价:

do
  arg <- s
  return . toIntSafe $ arg

对应

for {
      arg <- args
    } yield toIntSafe(arg)

哪个编译好。

要了解为什么您的示例无法编译,我们可以对其进行脱糖:

for {
      arg <- args
      result <- toIntSafe(arg)
   } yield result

=

args.flatMap { arg =>
  toIntSafe(arg).map {result => result}
}

现在查看类型:

args: List[String]
args.flatMap: (String => List[B]) => List[B]
arg => toIntSafe(arg).map {result => result} : String => ErrorM[Int]

这说明了问题。 flatMap 期待一个返回 List 的函数,但你给它一个返回 ErrorM.

的函数

Haskell 代码如下:

do
    arg <- s
    result <- toIntSafe arg
    return result

由于大致相同的原因无法编译:尝试绑定两个不同的 monad,List 和 Either。

scala 中的 for comprehension will 或 haskell 中的 do 表达式只会对相同的底层 monad 起作用,因为它们基本上都是对一系列 flatMap 和 [=分别为 20=]s。而那些仍然需要进行类型检查。

如果你想编写 monad,你可以使用 monad 转换器 (EitherT),尽管在你上面的例子中我认为你不想这样做,因为你实际上想在最后排序。

最后,在我看来,最优雅的代码表达方式是:

def convert(args: List[String]) = args.traverse(toIntSafe)

因为map后面跟着sequencetraverse