Hiding import behaviour

Austin Seipp austin at well-typed.com
Sat Oct 18 18:33:16 UTC 2014


On Sat, Oct 18, 2014 at 1:02 PM, htebalaka <goodingm at gmail.com> wrote:
> On 10/17/14 12:32, Alexander Berntsen wrote:
>> On 17/10/14 00:40, Austin Seipp wrote:
>> > Maybe there are some cases today where something like this could
>> > happen, but this seems awfully, awfully implicit and hard-to-follow
>> > as a language feature.
>> >
>> > In general I think a program that has imports like this that may
>> > clash can be automated to make it easier to manage - but ultimately
>> > such imports tend to represent a complex relationship between a
>> > module and its dependencies - I'd prefer it if these were as clear
>> > as possible.
>> Very strong +1 from me. It seems awfully implicit and obscure for very
>> little benefit, and it may mean quite a bit of work for tool developers.
>
> I guess my central point is I don't see how anyone can benefit from the
> current behaviour. For instance, a simple real world example:
>
> import Prelude
> import Data.Text.Lazy.IO (putStrLn)
>
> Regardless of the ordering of the imports, is there any way for me to use
> putStrLn in any context without hiding it from the Prelude (and any other
> modules that I might be unintentionally importing it from)?

I suppose my point isn't that the current behavior is more useful, but
the *proposed behavior seems more confusing for humans*. I would
rather have GHC inform me of an ambiguous import as opposed to
silently accepting or rejecting my program based on the import list,
and whether it shadows something prior to it. I don't even always know
what identifiers may get imported in the first place, due to
transitive module reexports. It just seems like pretty confusing
behavior - shadowing of identifiers is rarely a 'feature' for humans,
IMO.

In the example you have, what happens if I change the import list of
Data.Text by removing it, for example, or what happens if I *remove*
the Prelude import, and stick it after the Text import? Rather than
getting an out of scope identifier error, or something ambiguous, I'd
get a confusing type error based on Prelude's use of putStrLn in the
context of needing Texts', because the shadowing would fail to apply
since it didn't occur before the Text import. Shadowing of previously
imported identifiers only works one-way, so to speak, where with
'hiding', order no longer matters in the import list.

Of course you might say, "Well, of course Prelude exports putStrLn, so
you wouldn't move the import, and it wouldn't be a problem". The
problem is I don't know what exports an arbitrary module has; it
doesn't seem to scale mentally for humans at all. In this case, I know
Prelude exports that, but in the general case of:

import Frob
import Knob (xyz)

Today, this means I only import 'xyz' from Knob, and there are no
other ambiguous names. But under your proposal, I have zero clue if
'xyz' is actually shadowing a prior import. So unless I check *all*
the transitive exports of 'Frob', I have no clue if it's actually safe
to move the import of 'Knob' higher up - an identifier may not be
shadowed if I do that. OTOH, I know *for a fact* when I see this:

import Frob hiding (xyz)
import Knob (xyz)

which 'xyz' I'm referring to later, without ambiguity. Also, what
happens if I do this:

import Knob (xyz)
import Frob

legitimately, without shadowing, and 'Frob' later ends up exporting
its own 'xyz'? Do I just get an ambiguous identifier error, like I
would today? Again, shadowing in this sense only works 'one-way': top
to bottom, and it fails any other case they might be rearranged.

This all just seems like a relatively large amount of hoops to jump
through, just to avoid writing 'hiding' in a on a few things, so to
me, the cure looks worse than the disease. But I may just be missing
something completely.

> Any unqualified use will be ambiguous, unless you hide it from every other module that might
> export a function with the same name. I would think the fact that it
> shouldn't be implicitly imported from other modules would directly follow
> from the fact you imported it explicitly (otherwise, why did you import
> it?). I'm having trouble coming up with a single example where the current
> behaviour is useful.
>
> I can't speak to tooling, though I suppose if this doesn't get implemented
> I'll write my own. Just to be very clear, supposing you have some Import
> datatype which stores a list of any identifiers that are being explicitly
> imported unqualified (or conversely, a list of any identifiers that are
> being hidden), then the behaviour I'm suggesting is a pragma to enable
> something like this:
>
> hide :: [Import] -> [Import]
> hide = flip (fmap fmap appendHiddenImports) <*> collectOnly where
>     collectOnly :: [Import] -> [Identifier]
>     collectOnly = concat . mapMaybe getExplicitImports
>     appendHiddenImports :: [Identifier] -> Import -> Import
>     getExplicitImports :: Import -> Maybe [Identifier]
>
> where appendHiddenImports would only change import statements that import an
> unspecified number of unqualified identifiers, like "import X hiding (x, y)"
> or "import Y".
>
>
>
> --
> View this message in context: http://haskell.1045720.n5.nabble.com/Hiding-import-behaviour-tp5758155p5758246.html
> Sent from the Haskell - Glasgow-haskell-users mailing list archive at Nabble.com.
> _______________________________________________
> Glasgow-haskell-users mailing list
> Glasgow-haskell-users at haskell.org
> http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
>

-- 
Regards,

Austin Seipp, Haskell Consultant
Well-Typed LLP, http://www.well-typed.com/


More information about the Glasgow-haskell-users mailing list