带有隐式Monad的Monad理解失败,使用继承吗?

时间:2019-06-20 14:50:10

标签: scala monads scalaz

我在斯卡拉https://github.com/scala/bug/issues/2823碰上这张著名的10岁门票

因为我期望理解能像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")))

我得到了错误

  

“值映射表不是ErrorM [Int]的成员”   结果<-toIntSafe(arg)

如何恢复我惯常的美丽单调的理解?一些研究表明,FilterMonadic[A, Repr]抽象类是一个扩展类,如果您想成为一个理解者,可以将FilterMonadic与scalaz结合使用的任何示例?

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

  2. 我是否可以保留我的类型别名,而不必重新包装ErrorM的大小写类?

使用Scala 2.11.8

编辑:我添加了Haskell代码,以表明它确实在GHC中确实有效,而没有显式的Monad转换器,仅遍历和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"]

1 个答案:

答案 0 :(得分:2)

您的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中理解A或在haskell中执行do表达式仅适用于相同的基础monad,因为它们基本上都是分别对flatMap>>=系列的句法翻译。那些仍然需要进行类型检查。

如果您想组成单子,则可以使用单子转换器(EitherT),尽管在上面的示例中,我认为您不想这样做,因为您实际上想在最后进行排序。

最后,我认为表达代码的最优雅的方式是:

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

因为map后跟sequencetraverse