I am running into this famous 10 year old ticket in Scala https://github.com/scala/bug/issues/2823
Because I am expecting for-comprehensions to work like do-blocks in Haskell. And why shouldn't they, Monads go great with a side of sugar. At this point I have something like this:
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")))
And I'm getting the error
"Value map is not a member of ErrorM[Int]" result <- toIntSafe(arg)
How do I get back to beautiful, monadic-comprehensions that I am used to? Some research shows the FilterMonadic[A, Repr]
abstract class is what to extend if you want to be a comprehension, any examples of combining FilterMonadic
with scalaz?
Can I reuse my Monad implicit and not have to redefine map, flatMap etc?
Can I keep my type alias and not have to wrap around Either or worse, redefine case classes for ErrorM?
Using Scala 2.11.8
EDIT: I am adding Haskell code to show this does indeed work in GHC without explicit Monad transformers, only traversals and default monad instances for Either and List.
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"]
Monad
coming from? What are your imports? At a glance this is probably the fact thatEither
isn't right-biased prior to 2.12 but you haven't given us enough information to tell. – Travis Brownargs traverse toIntSafe
produce essentially the same result, only faster? Also, why are you passingMonad[ErrorM]
andTraverse[List]
from the outside. Do you expect to use this method for some otherMonad
andTraverse
implementations that aren't the constants from the library? – Andrey Tyukinargs: List[String]
andtoIntSafe(arg): ErrorM[Int]
, and the for comprehension above translates intoargs.flatMap( (arg:String) => toIntSafe(arg).map( (result:Int) => result ) ))
which is likely why it complains about themap
not existing. But even if it were the equivalent (assuming reasonable implementation ofmap
)args.flatMap( (arg:String) => toIntSafe(arg))
, it still shouldn't type-check. – Mor A.List
(one monad) and handle errors usingEither
(another monad) - to do both of that in one monad (which would allow doing it in one for comprehension) you would need a monad transformer e.g.EitherT[List, String, ?]
- that is true in Haskell as well and no implicit magic would align this for you. – Mateusz Kubuszok