Announcing Imp, a GHC plugin for automatically importing modules

TL;DR:

{-# OPTIONS_GHC -fplugin=Imp #-}
main = print $ Data.Tuple.swap ("world", "hello")
-- ("hello","world")
16 Likes

This is so cool! I could imagine using it globally to set up a set of “prelude” qualified modules for all my code (containers for instance)

2 Likes

Yes! That’s exactly how I imagine it being used. A package description might have something like this:

library
  ghc-options:
    -fplugin=Imp
    -fplugin-opt=Imp:--alias=Data.Map.Strict:Map
    -fplugin-opt=Imp:--alias=Data.Map.Lazy:LazyMap
    -fplugin-opt=Imp:--alias=Data.Sequence:Seq
    -fplugin-opt=Imp:--alias=Data.Set:Set
    -- ...

One thing I’d like to add to Imp is a convenient way to set up common aliases without having to list them all out manually like that.

5 Likes

Thank you, thank you, thank you!! This is a huge leap forward to fixing the Prelude problem. It’s also a great first step for beginners into the world of GHC plugins.

If I might suggest adding the config you mentioned above to the README, I think that would make it even easier to get started using this plugin effectively!

3 Likes

Wonderful! Potential game changer! :rocket:

It will be good to document the current module to package mappings, and how to adjust those, and how/requirements to use this with scripts.

stack script does some of this - perhaps there is overlap.

It would be great to make the module to package mappings deterministic and repeatable, as with stack. I guess this one will look for any version of the required package, that’s already installed.

1 Like

Yeah, Imp should be able to help at least a little with custom preludes, assuming that you want things to be qualified. It doesn’t help at all with unqualified things. Maybe it’s because I’ve spent too much time with Elm, but that doesn’t bother me too much.

I opened a pull request for adding the recommended usage to the README:

I’m not totally sure what you mean here. Imp doesn’t do any mapping between packages and modules. It just looks for qualified identifiers, gets the module names from them, and imports them if they’re missing. You still have to specify dependencies on packages in your *.cabal (or package.yaml) file.

stack script is a little different in that it will try to automatically determine package dependencies based on your import declarations. I don’t actually know if that works nicely with Imp. I haven’t used Stack in a while. Can some Stack users try it out and let me know how it goes?

Sorry for asking lazily without trying, but can the aliases themselves also have multiple components, like

-fplugin-opt=Imp:--alias=Foo.Bar.Baz.Quux:Lol.Quux?

1 Like

Yes, that’s fine. The only limitation is that both the source and target have to be valid module names. You can even clobber names if you really want to confuse people: -fplugin-opt=Imp:--alias=Data.HashMap.Strict:Data.Map.

1 Like

I guess I don’t understand it yet. I am wondering eg if I use Data.Tuple.swap in code, and that module and symbol exists in multiple packages on hackage, perhaps both installed on my machine, how would it disambiguate.

It’s the same as if you said import Data.Tuple instead. Something else is responsible for determining which packages are in scope, most often a package description (*.cabal or package.yaml file).

In a module you could use the PackageImports extension to disambiguate, like import "base" Data.Tuple. But Imp doesn’t (yet?) support that.

Imp is quite literally only inserting import declarations for you. If you enable Imp and used Data.Tuple.swap somewhere, it’s exactly the same as if you said import qualified Data.Tuple before that use of Data.Tuple.swap.

1 Like

Is it correct to put it another way: Imp automatically imports all the modules that your package knows about, qualified (and implicitly disables any unused import warnings for modules that are only imported implicitly)?

1 Like

That’s a great clarifying question!

There’s another plugin called qualified-imports-plugin that works that way. It automatically imports modules for you, regardless of whether they’re used or not.

Imp does not work that way. It will only add imports for things that are actually used in the module. Although the available packages and modules may technically be available to Imp, it doesn’t make use of that information. Instead it looks for qualified identifiers that don’t have a corresponding import declaration and then adds imports for them.

Both qualified-imports-plugin and Imp mark the imports as “implicit”, which prevents GHC from warning that they’re unused. However I believe that the qualified-imports-plugin approach will prevent GHC from warning you when the entire package is unused (via -Wunused-packages) since modules are actually imported.

1 Like

What happens if an “auto-import” forms a cycle of imports?

1 Like

I ran into some surprises while answering this question! I was originally going to say that a cycle would be the same whether it happened with explicit imports or with Imp. But it turns out that you can’t actually use Imp to import modules from the same unit. That’s surprising!

On the one hand, that means you simply can’t create a cycle with Imp. On the other hand, it means Imp is slightly less useful since you can’t use it to import other modules from the same unit.

I never ran into this problem when testing because I was always importing modules from other packages, like Data.Set.fromList. I created an issue to see if there’s a way to work around this problem:

My concern was more “boring” - a user of Imp subsequently being confronted with a error message about a large SCC of modules formed by the extra imports Imp added:

that’s quite clever! Thank you for releasing this.

2 Likes

Seconding the thanks. It’s really useful to have this as a plugin because that’s a much shorter path to getting the feature in the hands of users than going through a GHC proposal.

2 Likes