Reputation: 33861
I'm trying to understand the rules for do
notation.
Here is some code that typechecks:
fff :: Maybe Int
fff = do
_ <- pure $ Just 100
(+10)
<$> Just 50
Which is basically fff = (+10) <$> Just 50
. I would assume the above could would not type check - because surely each line should be within the context of Maybe
which (+10)
is not.
Why does the above typecheck? Here is a simpler example of the above:
fff :: Int -> Maybe Int
fff i = do
(+10)
<$> Just i
Why is the above considered valid syntax? Does that not 'desugar' to:
fff i = ((+10) >>= (\i -> fmap (Just i))) i
Which indeed gives a typecheck error in ghci.
Here is an example that does not typecheck following a similar indentation as above:
x :: Maybe Int
x = do
_ <- Just 1
undefined
<$> Just 5
(Thanks to @cvlad from the FP slack chat for the above example)
Upvotes: 3
Views: 145
Reputation: 71070
fff :: Int -> Maybe Int
fff i = do
(+10)
<$> Just i
Why is the above considered valid syntax?
Because it is parsed as
fff i = do { -- do { A } is just
(+10) } -- A
<$> Just i
which is equivalent to
fff i =
(+10)
<$> Just i
because <$> Just i
on its own is an invalid expression (so fff i = ((+10) >>= (\i -> fmap (Just i))) i
is incorrect translation), and that delimits the extent of the do
block as per the rule quoted in @chi's answer.
Indeed its type is inferred as
fff :: Num b => b -> Maybe b
You second example works if you add a space before the <$>
in the last line. Without the space, it is again parsed as
inputTest :: FormInput -> IO (Either [String] (Int, Int))
inputTest fi = do {
allErrors' <- undefined :: IO [String]
undefined }
<$> ((liftM2 ) (,) <$> undefined <*> undefined) fi
because <$> ...
on its own is invalid expression. Indeed when I add the explicit separators,
inputTest2 :: String -> IO (Either [String] (Int, Int))
inputTest2 fi = do {
allErrors2 <- undefined :: IO [String] ;
undefined }
<$> ((liftM2 ) (,) <$> undefined <*> undefined) fi
I get the exact same error message on TIO (had to use String
instead of your type there).
Since the first undefined :: IO [String]
, the whole do block has some IO t
type, and we can't fmap that over anything.
Always add all the explicit separators (in addition to practicing good indentation style), to avoid this weird syntax brittleness.
Your new example is
x :: Maybe Int
x = do -- { this is
_ <- Just 1 -- ; how it is
undefined -- } parsed
<$> Just 5
The code changed, but the answer is the same. The do
block before the <$>
is Maybe t
(because of Just 1
), and we can't fmap that.
Again, indent the last line some more and it'll compile, because undefined <$> Just 5
will now be parsed as one expression.
Upvotes: 4
Reputation: 116139
This is a weird interaction.
I started to simplify the test case to this, which runs fine.
> x = do succ ; <$> Just 1
> x
Just 2
By comparison, this does NOT parse:
> y = do { succ ; <$> Just 1 }
error: parse error
However, this parses:
> z = do { succ } <$> Just 1
> z
Just 2
So, here's what I think is going on. Since token <$>
can never start an expression, parse tentatively fails. The do
parser rule is, essentially, a maximum munch rule: on a fail, add an implicit }
and try again.
Because of this, x
above is parsed as z
. Since succ
is a monadic value (line (+10)
in OP's question) it can appear inside do
. This makes type check succeed.
Quoting the Haskell Report 2.7
A close brace is also inserted whenever the syntactic category containing the layout list ends; that is, if an illegal lexeme is encountered at a point where a close brace would be legal, a close brace is inserted.
Upvotes: 4