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

标签 scala monads scalaz

我在 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] 抽象类是扩展什么,如果你想成为一个理解,任何结合 FilterMonadic 与 scalaz 的例子?

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

  2. 我能否保留我的类型别名而不必绕回 Either 或者更糟的是,为 ErrorM 重新定义案例类?

使用 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

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

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

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

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

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

因为 map 后接 sequencetraverse

关于scala - Monad for-comprehensions with implicit Monad 失败,使用继承?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56688609/

相关文章:

java - 将 java 属性传递给 scala - nosuchmethod 错误

c++ - 继续延续 monad 元组。怎么了?

scala - 状态 monad 的 future

scala - 标记类型中的协方差

haskell - 编写简单的 QuickCheck URL 生成器时嵌套 monad 的问题

scala - 在 Scalaz 状态单子(monad)中查找 Shapeless HList 的类型类实例

scala - Scala 的 actor 是否类似于 Go 的协程?

scala - 使 sbt 常量具有包含在测试和集成测试中的依赖项

即使 Json.toString 返回正确的值,Json.asString 也返回 None

haskell - 创建一个 monad 以增量存储类型不匹配的结果