[ghc-steering-committee] #216: Qualified Do again, recommendation: accept the alternative

Joachim Breitner mail at joachim-breitner.de
Thu Apr 9 17:16:51 UTC 2020


Dear Committe,

Proposal: 
https://github.com/tweag/ghc-proposals/blob/local-do/proposals/0000-local-do.rst
Discussion (long, sorry):
https://github.com/ghc-proposals/ghc-proposals/pull/216


Summary:

Over a year ago (on my birthday then) Arnaud created  a “local do”
proposal that would be a more targetted variant of RebindableSyntax,
just for “do”. In June, we sent it back because a simple syntactic
desugaring to records didn’t quite seem right (bad type inference).

In March, the authors can back with an alternative, which was using a
Module name instead of a value of record type to indicate that monadic
operations to use. This nicely solved the type system issues and meant
that the translation can happen (in principle) in the parser or renamer
stage. But some of us noticed that a builder record is nicer after all,
and we can fix the type system issues, mostly by introducing a new concept
of “fully settled type”; with analogies to TH stage restrictions.

The authors updated the proposal accordingly, but also list the alternatives
in the documents.

Based on the GitHub thread we have varying opinions among the committee.
Nevertheless, I think the authors have done a great and patient job so far, so
we owe them a hopefully conclusive discussion.

The main question we have to decide is:

    record-based   or    module based


Record based:
 ➕ A single entity one can import, reexport, even rebind
 ➕ A single entity that can carry the documentation
 ➕ One module can export multiple builders
 ➕ Looking forward, the builder could be dynamically constructed
   (i.e. a local value)
 ➕ Concept of fully settled may be useful elsewhere in the future
   and can be expanded
 ➖ Needs a new concept of “fully settled” that we don’t have elsewhere
 ➖ Initially, “fully settled” introduces staging restrictions;
   builder values may not be usable everywhere where they are in scope.
 ➖ Lots of fluff on the defining side
   (define a likely one-off record + a value)
 ➖ May require extensions (e.g. RankNTypes, ImpredicativeTypes)
   on the defining side, even for a builder for the “normal” Monad
   (see https://github.com/ghc-proposals/ghc-proposals/pull/216#issuecomment-600746723
   for an example for the previous two points)
 ➕ Some compositionality (functions modifying builders), but
 ➖ not as universal as one would hope, as there is not a single builder type
   (different qualified monads likely use different record type)
 ➕ Can support “passing arguments to do” via `(monadBuilder @Maybe).do`
   or `(b cfg).do`
   (once the notion of “fully settled” is powerful enough)

Module based:
 ➕ Simpler to specify and understand:
   Only affects parsing, possibly renaming. No interactions with the type system.
 ➕ Works out of the box with, say, `Prelude.` as the qualifier
 ➕ Benefits from future improvements to the module system
 ➖ Would need separate syntax for “passing arguments to do”, should we want that
 ➕ But if we had that, it can implement the record-based approach, by passing a
   recoord to a suitable qualified do monad, as Iavor observes:
   https://github.com/ghc-proposals/ghc-proposals/pull/216#issuecomment-598859245

The module-based approach would additionally raises the question whether
 * the desugaring to M.(>>) means (>>) as provided by (some) M”,
   akin to how plain do notation works.
 * the desugaring to M.(>>) means just that (and requires (>>) to be imported as well),
   akin to how RebindableSyntax works


There was also a brief discussion of whether this should extend the set
of operations involved to a `last` function that is used in the
translation rule for a single-statement do notation, but it did not
catch on.


Recommendation:

While both approaches are reasonable and have their merits, I recommend
to accept the Module based approach. It supports most use-cases
presented so far, in particular the Linear.do as envisioned by the
authors, so it seems good enough™.
Furthermore, it certainly is significantly simpler, given that it can
be specified purely in terms of naming things, so we have a higher
chance that this will work well with other existing and future language
features.

Should the committee follow that decision, I recommend to pick the
variant where the value does not need to be in scope, so that its
mechanism is close to the normal do notation, and that you can write

 import Linear (runLinear, other, stuff)
 …
   Linear.do { … }

without mucking with qualified imports or shadowing (>>). It seems odd
to require the user to add ((>>), (>>=), fail) to an import list when
you don’t actually mention that name anywhere.




Cheers,
Joachim


-- 
Joachim Breitner
  mail at joachim-breitner.de
  http://www.joachim-breitner.de/




More information about the ghc-steering-committee mailing list