Skip to content

NoFieldSelectors #160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Aug 23, 2019
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
946eb8b
NoToplevelFieldSelectors
reactormonk Aug 11, 2018
80d51cc
added possible alternative designs
reactormonk Aug 15, 2018
fdb3b68
Cleaned up unresolved questions
reactormonk Sep 4, 2018
7312734
Expanded Possible Alternative Designs
reactormonk Sep 8, 2018
998db59
Added annotation examples
reactormonk Sep 8, 2018
2a7296f
Clarified export statement difference
reactormonk Sep 8, 2018
0fa473c
Added comment about writing selector functions by hand is now different
reactormonk Sep 8, 2018
884de0f
Clarified HasField, added unresolved questions
reactormonk Nov 1, 2018
ed2b2ee
Specified how the TH function would look like
reactormonk Nov 1, 2018
7b6f271
Deleted the NoToplevelFieldSelectors pragma.
reactormonk Dec 2, 2018
e1a451e
Added implementation plan
reactormonk Jun 2, 2019
8f7fdba
Addressed various feedback
reactormonk Jun 15, 2019
3d3e81b
Another feedback round
reactormonk Jul 2, 2019
ccc5f2b
Expanded examples & changed TH function to mkFieldSelector
reactormonk Jul 10, 2019
3248723
Removed NameSpace extension
reactormonk Jul 10, 2019
3d95a8d
+ example C
reactormonk Jul 11, 2019
2abbe82
tighten up the language a bit more
gridaphobe Jul 11, 2019
74dc0a0
Merge pull request #1 from gridaphobe/no-field-selectors
reactormonk Jul 12, 2019
d801768
Revamped Examples, added TH question
reactormonk Jul 14, 2019
3539e90
+ Corrections
reactormonk Jul 16, 2019
b1f9920
Imply DuplicateRcordFields / no specific TH treatment
reactormonk Jul 26, 2019
16f6a0b
No more DuplicateRecordFields implication
reactormonk Aug 4, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions proposals/0000-no-toplevel-field-selectors.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
NoFieldSelectors
==============

.. proposal-number:: Leave blank. This will be filled in when the proposal is
accepted.
.. trac-ticket:: Leave blank. This will eventually be filled with the Trac
ticket number which will track the progress of the
implementation of the feature.
.. implemented:: Leave blank. This will be filled in with the first GHC version which
implements the described feature.
.. highlight:: haskell
.. header:: This proposal is `discussed at this pull request <https://github.com/ghc-proposals/ghc-proposals/pull/160>`_.
.. sectnum::
.. contents::

Enabling this Language Extension removes the default of Haskell data
declarations to generate toplevel field accessor functions for records, such
that the user can define their own via Generic or positional extraction.

Motivation
------------

There have been proposals to extend the usage of records in Haskell
(DuplicateRecordFields, OverloadedRecordFields, DeriveGeneric to name a few),
but none of them address the original issue where record fields steal the
toplevel function name for each field for selector purposes. This proposal
intends to open the design space to this issue by removing the generation of
these toplevel selector functions.

Possible Alternative Use Cases for Record Names
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

After removing the toplevel selector names, the field names could be used as
bindings for other values.

- A `generic-lens` equivalent for toplevel lenses
- namespaced accessors
- namespaced lenses
- overloaded-labels based accessors / lenses
- row types
- `<https://github.com/ghc-proposals/ghc-proposals/pull/158>`_
- ...

Proposed Change Specification
-----------------------------

This proposal introduces a new extension ``FieldSelectors`` that controls the
generation of toplevel record field selector functions. The extension is enabled
by default to match the current behavior, but may be disabled per-module with
the ``NoFieldSelectors`` language pragma.

When ``NoFieldSelectors`` is enabled, Record construction/update syntax and
pattern matching will work as before, as will the disambiguation handled by
``DuplicateRecordFields``.

Record fields will still be part of a ``Record(..)`` export, or can also be
named individually. They always have to be associated with the data type though,
because there is no more toplevel selector (see `Example`_).

Template Haskell should not rely on the selectors being present, and should use
named pattern matching instead.

Because field labels and toplevel selectors are now different entities,
import/export lists now behave differently. Names listed with the constructor
(e.g. ``Record(field)``) refer to the field, whereas direct mentions ``field``
refer to a function named ``field``. Without setting ``NoFieldSelectors``, these
two would be equivalent.

Examples
--------

Given a data structure

data Foo = Foo { bar :: Int, baz :: String }

The following will be available:

1. the type constructor ``Foo``
2. the data constructor ``Foo``
3. the fields ``bar`` and ``baz`` for record construction, update, and patterns
4. the two functions ``bar`` and ``baz``, which are ``Foo -> Int`` and ``Foo -> String``

If the language extension ``NoFieldSelectors`` is enabled, items (1), (2), and (3)
will still be generated, but (4) will not.

Wildcard exports will work as before, except for the two functions. Even if
these functions are otherwise defined, the wildcard will not export them.
Exporting the names for record construction now has to be specific to the
record. Without ambiguitiy, previously this was equivalent

.. code-block:: haskell

module A where (Foo(Foo, bar, baz))
data Foo = Foo { bar :: Int, baz :: Int }

.. code-block:: haskell

module B where (Foo(Foo, bar), baz)
data Foo = Foo { bar :: Int, baz :: Int }

Under ``NoFieldSelectors``, these two export statements are now different. The
first one will export the field ``baz``, but not the function ``baz``, while the
second one will export the function ``baz`` (assuming one is defined), but not
the field ``baz``. Because of this change, writing out all selector functions by
hand is still different, because they all have to be exported separately.

.. code-block:: haskell
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These code samples don't appear to be rendering at all in the rich-text view.


{-# LANGUAGE NoFieldSelectors #-}
module Exports where (Foo(Foo, bar, baz))
data Foo = Foo { bar :: Int, baz :: Int }

bar (Foo x _) = x
baz (Foo _ x) = x

is different from

.. code-block:: haskell

module Exports where (Foo(Foo, bar, baz))
data Foo = Foo { bar :: Int, baz :: Int }

Because the functions in the first example don't get exported.

Let's take a module ``A`` with a function with the same name as a field, with
the extension enabled:

.. code-block:: haskell

{-# LANGUAGE NoFieldSelectors #-}
module A where (Foo(Foo, bar, baz))
data Foo = Foo { bar :: Int, baz :: Int }
baz = 42

Which would be equivalent to:

.. code-block:: haskell

{-# LANGUAGE NoFieldSelectors #-}
module A where (Foo(..))
data Foo = Foo { bar :: Int, baz :: Int }
baz = 42

A second module, ``B``, which does not export the selector ``baz`` of
constructor ``Foo``, but instead exports the toplevel binder ``baz``. The fields
can still be used when exported (as in module ``A``).

.. code-block:: haskell

{-# LANGUAGE NoFieldSelectors #-}
module B where (Foo(Foo, bar), baz)
data Foo = Foo { bar :: Int, baz :: Int }
baz = 42

Using ``baz`` as a field when importing ``B`` will fail, because the field
``baz`` is not in scope anymore, because it is not exported by ``B``.

.. code-block:: haskell

import B
foo = Foo 23 42
foo { baz = 1 }

However, it is possible to use the imported variable ``baz``, because ``B`` exports it.

.. code-block:: haskell

import B
main = print baz

If you wanted to use both, you'd have to export both explicitly:

.. code-block:: haskell

{-# LANGUAGE NoFieldSelectors #-}
module C where (Foo(Foo, bar, baz), baz)
data Foo = Foo { bar :: Int, baz :: Int }
baz = 42

Now ``baz`` here assigns the value ``42`` to the field ``baz``.

.. code-block:: haskell

import C
foo = Foo 23 1
foo { baz = baz }


Effect and Interactions
-----------------------

`HasField` will work as before, if the corresponding field has been exported. It
doesn't need to be exported as function.

Breakage estimation
^^^^^^^^^^^^^^^^^^^

Enabling this extension will beak Template Haskell which assumes the presence of
a field selector. Use named pattern matching instead.

Anything that generates code with the help of Generic should be fine. The same
functionality that generates the anonymous functions for Generic could be used
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean "the same functionality that generates the anonymous functions for TH could be used to provide Generic functionality". This is the first mention of Generic in the proposal :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it the same in the end? There's a function somewhere which allows you to create a record selector, and it'll be used by Generic and TH. Maybe rewrite the paragraph altogether?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, it's a very minor comment.

to provide TH functionality to replace the existing toplevel functions.

The record extensions NamedFieldPuns, RecordWildCards, DisambiguateRecordFields,
and DuplicateRecordFields are unaffected by this change.


Costs and Drawbacks
-------------------

This might cause some confusion that record fields can't be accessed by toplevel
selectors anymore - however, that shouldn't be too big of an issue, because some
library authors already stopped exporting these selectors so they don't have to
break downstream software on record changes.


Alternatives
------------

None.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As specified, this extension applies at the definition sites of fields. One could imagine making it apply at use sites instead, so that a module could take selector names out of scope (as variables in expressions), even if they come from imported modules that don't use the extension. Is that worth considering?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then you'd need an orphan Generic instance, or to enable one of the language extensions to use them. Might be an interesting addition to use old datatypes with this approach. When creating this library, I was mostly thinking of where you have control over the declaration sites, not where you don't. I'd still go with this proposal, and consider this to be an additional feature.



Implementation Plan
-------------------

I'm currently on the way of implementing this extension. It's roughly as
follows:

- Add new `NameSpace` to `OccName`: `RecordSelector String`
- Remove `flSelector` from `FieldLabel`, add an flag which denotes if it should
be found as `VarName`
- Remove `FlParent`
- Change any field lookup code to look for new `OccName`
- Implement `FieldSelector` flag to look for selectors if you're looking
for `VarName`
- Adjust `Generic` instances
- Add new `TH` function to access record selectors

Future Plans
------------

Make the behavior outlined in the discussion work:

.. code-block:: haskell

data Foo = Foo { foo :: Int } deriving selectors