我在 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 的例子?
我可以重用我的 Monad 隐式而不必重新定义 map、flatMap 等吗?
我能否保留我的类型别名而不必绕回 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
后接 sequence
是 traverse
关于scala - Monad for-comprehensions with implicit Monad 失败,使用继承?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56688609/