[ghc-steering-committee] #409: Exportable named defaults, Recommendation: Partial Accept

Richard Eisenberg lists at richarde.dev
Sun Jul 11 01:47:45 UTC 2021



> On Jul 9, 2021, at 12:35 AM, Eric Seidel <eric at seidel.io> wrote:
> 
> On Thu, Jul 1, 2021, at 13:16, Joachim Breitner wrote:
>> a different way to phrase that question might be: Do we want these
>> defaulting declarations to behave just exactly like named things, or
>> exactly like typeclass instances, or do we afford a new class with it’s
>> own exporting/importing behavior. Is that a fair assessment?
> 
> Not entirely, I think. 
> 
> We currently have two types of import/export behavior:
> named things, and typeclass instances. The proposal as currently
> written places defaulting rules somewhere in between: defaulting
> rules are exported like named things, but imported like class instances.
> This is new, but not too foreign, as the behavior on both sides exactly
> matches existing behavior we're familiar with. It's just the combination
> that's new.

This doesn't match my understanding of the proposal. It looks to me that, as written in the proposal, exports of a `default` would have to be explicit. That is, a module starting with `module M where ...` would not export any defaults. This fact is a bit implied in the proposal ("This proposal does not modify that behaviour: a default declaration by itself does not apply outside its module."), but it's my best understanding. 

---

Simon and I have discussed. We both came to an agreement that imports should have to be explicit.

GHC currently has two import/export strategies.

Strategy 1: Always. In the Always strategy, an entity is always exported from a module and always brought into scope from an imported module. The Always strategy is used for type and class instances.

Strategy 2: Public. In the Public strategy, an entity is exported by default (no export list) or when explicitly included in an export list. It is brought into scope from an importing module by default (no import list) or when explicitly included in an import list. A Public entity may be excluded from scope by a `hiding` clause. All top-level named entities are exported/imported via the Public strategy.

I propose (with Simon's support)

Strategy 3: Private. In the Private strategy. an entity is exported only when explicitly included in an export list, and it is brought into scope from an imported module only when explicitly included in the import list. I propose we use Private for `default` declarations (only).

Reasons:

* Changing defaulting behavior really can launch the rockets. Suppose T has a Num instance whose fromInteger uses unsafePerformIO to launch the rockets. Then including T in an import list could make a very innocent-looking `x = 5` declaration launch the rockets.

* GHC currently supports an option -ddump-minimal-imports, which displays import lists describing what symbols must be brought into scope from an imported module. If a `import M` import statement brought defaulting behavior into scope, then going from `import M` to `import M (foo, bar)` might deleteriously change defaulting behavior, thus invalidating the work of -ddump-minimal-imports.

* The proposal as written does not describe how `module` exports work with named defaults. For example, what happens in `module B (module A) where import A`? Normally, that re-exports all names in scope both as `A.blah` and as `blah`. But, of course, a default isn't named in this way. So is the default exported? By requiring explicit inclusion in the export list, the Private strategy sidesteps this question.

* This is a more conservative choice. We can always revisit this in the light of experience. However, if defaults were always imported, it would be much more disruptive to make them imported only by request.

We have rightly identified that using the Private strategy would potentially reduce the usefulness of this idea, especially with alternative Preludes. As far as I know, GHC does not currently officially support having an alternative Prelude. That is, an "alternative Prelude" is really just disabling the import of base.Prelude and then importing some other module. However, we could imagine a compiler flag that specifies another package (or module name) to use as the Prelude... and then we could also specify how it is imported. For example, we could say that the Prelude is imported with

> import Prelude
> import Prelude ( default(..) )

where the second line says to grab all the defaults. I think this would be reasonable, but not necessary in the first version of this current proposal.

Richard
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-steering-committee/attachments/20210711/1e998e13/attachment.html>


More information about the ghc-steering-committee mailing list