Records in Haskell

Yitzchak Gale gale at sefer.org
Mon Jan 16 19:16:08 CET 2012


Simon Peyton-Jones wrote:
> Johan, if you are serious, do add a new wiki page to
> describe the design.
> You say it's simple, but I don't think it really is.

I'll support Johan by presenting the proposal for A below.
I believe that it really is very simple, both in concept and
implementation, but correct me if I am wrong.

Of course, do not blame any of my errors on Johan, and
do not assume that he supports this unless he says so.

I'll support the consensus (so far) that B is the nicest.
But if we can't get to a design for B that will actually
be implemented within a reasonably short period of
time, then please, let's try not to get stuck again.
If this or some other design for A is indeed deemed
to be simple, let's just do that if we can't do B.

Perhaps we should set some time limit for B.
I propose the following time limit: if a design for A
has been tentatively approved and SPJ does not
contribute to the discussion about B for one full month,
then A automatically gets final approval and will be
implemented immediately.

[This has the additional advantage of giving SPJ
motivation to remain engaged, because he seems
to prefer B. :)]

Proposal for A:

Allow nested modules. A nested module can occur in any
position that a data declaration can occur. The syntax of
a nested module is the same as the syntax of a conventional
module.

A nested module must have the same hierarchical name as
the module in which it is nested, followed by a single additional
component. A name with a single component can be specified
in the declaration of a nested module; this is syntactic sugar for
the fully qualified name of the enclosing module followed by that
single additional component.

When a module M.A is directly nested in module M, there is
an implied import in the enclosing module M as follows:

import qualified M.A as A

and an implied import in the nested module M.A as follows:

import M

These implied imports may optionally be specified explicitly
with no effect, or overridden with other explicit imports,
similarly to the usual implied import of Prelude.

When modules are nested to a depth greater than one,
similar implied imports exist for all recursively enclosing
and enclosed modules, with the same rules about
overriding.

If an enclosing module M has an export list, a nested
module N at any depth recursively cannot be imported
by modules not nested inside M unless N is included in
the export list of M. If M does not have an export list,
N can be imported by any other module as usual.

In every other respect, a nested module declaration has
exactly the same effect as any other module declaration.
In particular, the behavior of nested modules in the
presence of all corner cases such as data families, etc.,
is specified by this rule.

The effect of a nested module on the behavior of
ghc --make is left unspecified as of now, until
there is feedback from the GHC team. This would
probably involve GHC looking for A.B and then
A in turn when it fails to find A.B.C. Or perhaps
even when A.B.C is found, to identify erroneous
duplication. Or GHC could stay pretty much as
it is now, relying on the user to ensure that GHC
finds the nested module; that would certainly
be fine for an initial implementation.

Usage example:

module Library where
  import Data.Text (Text)
...
  type ISBN = Text
  module Book where
    import Data.Text (Text)
    data T = New { name :: Text, iSBN :: ISBN }
  module Checkout where
    import Data.Time
    import qualified Library.Book as Book
    import qualified Library.Borrower as Borrower
    data T = New
      { book :: Book.T, borrower :: Borrower.T, dueDate :: Day }
  module Borrower where
    import Data.Text (Text)
    data T = New { name :: Text, address :: Text }
  module History where
    import qualified Library.Borrower as Borrower
    import qualified Library.Checkout as Checkout
    data T = New { borrower :: Borrower.T, checkouts :: [Checkout.T] }

This makes available in the module Library the
record types:

Book.T, Checkout.T, Borrower.T, History.T

with constructors:

Book.New, Checkout.New, Borrower.New, History.New

and record accessors:

Book.name, Book.iSBN,
Checkout.book, Checkout.borrower, Checkout.dueDate,
Borrower.name, Borrower.address,
History.borrower, History.checkouts

I believe this specification should be very simple to
implement and describe. There are some obvious
shortcomings. But it does provide basic namespacing
of records with almost no change to Haskell and GHC.

Note also that you need to be careful to avoid mutually
recursive imports. That is really more of a limitation
of GHC than a limitation of the specification itself.
Other compilers might not require that.

I'd be happy to hear ideas about how to develop this
simple idea further and eliminate some of the
shortcomings, on condition that it doesn't lead to
further bikeshedding and significant delay.

One obvious enhancement would be for the
implied import of the enclosing module to
include also all names imported into
the enclosing module, unlike the usual
convention for imports. I'm not sure if
there are complications to that though.

Thanks,
Yitz



More information about the Glasgow-haskell-users mailing list