Scala equivalent of Haskell's do-notation (yet

2020-06-30 05:42发布

问题:

I know that Haskell's

do
  x <- [1, 2, 3]
  y <- [7, 8, 9]
  let z = (x + y)
  return z

can be expressed in Scala as

for {
  x <- List(1, 2, 3)
  y <- List(7, 8, 9)
  z = x + y
} yield z

But, especially with monads, Haskell often has statements inside the do block that don't correspond to either <- or =. For example, here's some code from Pandoc that uses Parsec to parse something from a string.

-- | Parse contents of 'str' using 'parser' and return result.
parseFromString :: GenParser tok st a -> [tok] -> GenParser tok st a
parseFromString parser str = do
  oldPos <- getPosition
  oldInput <- getInput
  setInput str
  result <- parser
  setInput oldInput
  setPosition oldPos
  return result

As you can see, it saves the position and input, runs the parser on the string, and then restores the input and position before returning the result.

I can't for the life of me figure out how to translate setInput str, setInput oldInput, and setPosition oldPos into Scala. I think it would work if I just put nonsense variables in so I could use <-, like

for {
  oldPos <- getPosition
  oldInput <- getInput
  whyAmIHere <- setInput str
  result <- parser
  ...
} yield result

but I'm not sure that's the case and, if it is correct, I'm sure that there must be a better way to do this.

Oh, and if you can answer this question, can you answer one more: how long do I have to stare at Monads before they don't feel like black magic? :-)

Thanks! Todd

回答1:

Yes, that translation is valid.

do { x <- m; n } is equivalent to m >>= \x -> n, and do { m; n } is equivalent to m >> n. Since m >> n is defined as m >>= \_ -> n (where _ means "don't bind this value to anything"), that is indeed a valid translation; do { m; n } is the same as do { _ <- m; n }, or do { unusedVariable <- m; n }.

A statement without a variable binding in a do block simply disregards the result, usually because there's no meaningful result to speak of. For instance, there's nothing interesting to do with the result of putStrLn "Hello, world!", so you wouldn't bind its result to a variable.

(As for monads being black magic, the best realisation you can have is that they're not really complicated at all; trying to find deeper meaning in them is not generally a productive way of learning how they work. They're simply an interface to compose computations that happen to be particularly common. I recommend reading the Typeclassopedia to get a solid grasp on Haskell's abstract typeclasses, though you'll need to have read a general Haskell introduction to get much out of it.)