Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this syntax as expressive as the do-notation?

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.

like image 210
MaiaVictor Avatar asked Dec 11 '22 11:12

MaiaVictor


2 Answers

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 do-block which doesn't end in return. [See Derek Elkins' comment.]

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 joins.

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 expression expr 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 lift expr as high as possible within its current scope, bind it to a fresh name x, and replace !expr with x. 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.

like image 89
Alexey Romanov Avatar answered Jan 11 '23 06:01

Alexey Romanov


How about this:

do a <- something
   b <- somethingElse a
   somethingFinal a b
like image 45
MigMit Avatar answered Jan 11 '23 04:01

MigMit