The do
notation allows us to express monadic code without overwhelming nestings, so that
main = getLine >>= \ a ->
getLine >>= \ b ->
putStrLn (a ++ b)
can be expressed as
main = do
a <- getLine
b <- getLine
putStrLn (a ++ b)
Suppose, though, the syntax allows ... #expression ...
to stand for do { x <- expression; return (... x ...) }
. For example, foo = f a #(b 1) c
would be desugared as: foo = do { x <- b 1; return (f a x c) }
. The code above could, then, be expressed as:
main = let a = #getLine in
let b = #getLine in
putStrLn (a ++ b)
Which would be desugared as:
main = do
x <- getLine
let a = x in
return (do
x' <- getLine
let b = x' in
return (putStrLn (a ++ b)))
That is equivalent. This syntax is appealing to me because it seems to offer the same functionality as the do-notation, while also allowing some shorter expressions such as:
main = putStrLn (#(getLine) ++ #(getLine))
So, I wonder if there is anything defective with this proposed syntax, or if it is indeed complete and equivalent to the do-notation.
putStrLn
is already String -> IO ()
, so your desugaring ... return (... return (putStrLn (a ++ b)))
ends up having type IO (IO (IO ()))
, which is likely not what you wanted: running this program won't print anything!
Speaking more generally, your notation can't express any [See Derek Elkins' comment.]do
-block which doesn't end in return
.
I don't believe your notation can express join
, which can be expressed with do
without any additional functions:
join :: Monad m => m (m a) -> m a
join mx = do { x <- mx; x }
However, you can express fmap
constrained to Monad
:
fmap' :: Monad m => (a -> b) -> m a -> m b
fmap' f mx = f #mx
and >>=
(and thus everything else) can be expressed using fmap'
and join
. So adding join
would make your notation complete, but still not convenient in many cases, because you end up needing a lot of join
s.
However, if you drop return
from the translation, you get something quite similar to Idris' bang notation:
In many cases, using do-notation can make programs unnecessarily verbose, particularly in cases such as
m_add
above where the value bound is used once, immediately. In these cases, we can use a shorthand version, as follows:m_add : Maybe Int -> Maybe Int -> Maybe Int m_add x y = pure (!x + !y)
The notation
!expr
means that the expressionexpr
should be evaluated and then implicitly bound. Conceptually, we can think of!
as being a prefix function with the following type:(!) : m a -> a
Note, however, that it is not really a function, merely syntax! In practice, a subexpression
!expr
will liftexpr
as high as possible within its current scope, bind it to a fresh namex
, and replace!expr
withx
. Expressions are lifted depth first, left to right. In practice, !-notation allows us to program in a more direct style, while still giving a notational clue as to which expressions are monadic.For example, the expression:
let y = 42 in f !(g !(print y) !x)
is lifted to:
let y = 42 in do y' <- print y x' <- x g' <- g y' x' f g'
Adding it to GHC was discussed, but rejected (so far). Unfortunately, I can't find the threads discussing it.
How about this:
do a <- something
b <- somethingElse a
somethingFinal a b
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With