wiki:ArgumentDo

Overview

This page documents a proposed syntactical extension called ArgumentDo. The feature request is tracked at #10843.

This extension would allow a do block, a lambda, and a few other syntactic constructs to be placed directly as a function argument, without parentheses or a $. For example,

atomically do
  v <- readTVar tv
  writeTVar tv $! v + 1

would be equivalent to

atomically (do
  v <- readTVar tv
  writeTVar tv $! v + 1)

and

withForeignPtr fptr \ptr -> c_memcpy buf ptr size

would be equivalent to

withForeignPtr fptr (\ptr -> c_memcpy buf ptr size)

Changes to the grammar

The Haskell report defines the lexp nonterminal thus (* indicates a rule of interest):

lexp  →  \ apat1 … apatn -> exp            (lambda abstraction, n ≥ 1)  *
      |  let decls in exp                  (let expression)             *
      |  if exp [;] then exp [;] else exp  (conditional)                *
      |  case exp of { alts }              (case expression)            *
      |  do { stmts }                      (do expression)              *
      |  fexp 

fexp  →  [fexp] aexp                       (function application)
 
aexp  →  qvar                              (variable)
      |  gcon                              (general constructor)
      |  literal
      |  ( exp )                           (parenthesized expression) 
      |  qcon { fbind1 … fbindn }          (labeled construction)
      |  aexp { fbind1 … fbindn }          (labelled update)
      |  …

which means lambda, let, if, case, and do constructs cannot be used as either LHS or RHS of a function application. GHC Haskell has a few more constructs that fall into this category, such as mdo, \case and proc blocks.

The ArgumentDo extension would allow all these constructs in argument positions. This is accomplished by moving their production rules under aexp:

lexp  →  fexp 

fexp  →  [fexp] aexp                       (function application)
 
aexp  →  qvar                              (variable)
      |  gcon                              (general constructor)
      |  literal
      |  ( exp )                           (parenthesized expression) 
      |  qcon { fbind1 … fbindn }          (labeled construction)
      |  aexp { fbind1 … fbindn }          (labelled update)
      -- Here are the moved rules
      |  \ apat1 … apatn -> exp            (lambda abstraction, n ≥ 1)  *
      |  let decls in exp                  (let expression)             *
      |  if exp [;] then exp [;] else exp  (conditional)                *
      |  case exp of { alts }              (case expression)            *
      |  do { stmts }                      (do expression)              *
      |  …

Now the lexp nonterminal is redundant and can be dropped from the grammar.

Note that this change relies on an existing meta-rule to resolve ambiguities:

The grammar is ambiguous regarding the extent of lambda abstractions, let expressions, and conditionals. The ambiguity is resolved by the meta-rule that each of these constructs extends as far to the right as possible.

For example, f \a -> a b will be parsed as f (\a -> a b), not as f (\a -> a) b.

Less obvious examples

Deleting parentheses

This extension will most often allow deletion of just one $ operator per application. However, sometimes it does more. For example, in the following example, you can't simply replace the parentheses with a $:

pi + f (do
  ...
  )

With ArgumentDo, you would be able to write the following instead:

pi + f do
   ...

Multiple block arguments

A function may take multiple do blocks:

f do{ x } do { y }

or

f
  do x
  do y

Block as a LHS

A do block can be LHS of a function application:

do f &&& g
x

would just mean

(f &&& g x

Design space

Possible modifications to the proposal include:

  • Only allow do in argument positions, but no other constructs. This has an advantage of making a minimal change to the grammar, while addressing the most common case.

This proposal has been extensively discussed on haskell-cafe and on reddit.

On the mailing list I see roughly 13 people in favor of the proposal and 12 people against it. Some major opinions (mostly copied from bgmari's summary).

Pros

  • It's easier to read than the alternative.
  • This extension removes syntactic noise.
  • This makes basic do-syntax more approachable to newbies; it is a commonly asked question as to why the $ is necessary.
  • This simplifies the resulting AST, potentially making it simpler for editors and other tools to do refactoring.
  • It's something that belongs in the main language, and if its something we'd consider for a mythical Haskell', it has to start as an extension.
  • It gets rid of some cases where using $ doesn't work because $ interacts with other infix operators being used in the same expression.
  • This would make do blocks consistent with record creation, where parentheses are skipped, allowing things such as return R { x = y}
  • This does not change the meaning of any old programs, only allows new ones that were previously forbidden.
  • This gets rid of the need for a specially-typed $ allowing runSt $ do ...
  • It allows unparenthesized non-trivial application arguments not only as the last argument (using $), but for all arguments, in separate lines, when headed by do or another group A construct.
  • It makes the language more regular by reducing the number of nonterminals by one.

Cons

  • It's harder to read than the alternative.
  • Creating a language extension to get rid of a single character is overkill and unnecessary.
  • You can already get rid of the $ by just adding parentheses.
  • More and more syntactic "improvements" just fragment the language.
  • Although this is consistent with record syntax, record syntax without parents was a mistake originally.
Last modified 17 months ago Last modified on Jul 13, 2016 8:30:20 AM