Module Import Syntax Sugar Proposal

This is a proposal for a syntax sugar for module imports that I think would fix most (all?) of the pain points of importing modules as well as potentially enable a decent solution to purescript#2437.

Pain Points

  • Module names are very long.
  • Modules are imported as unqualified by default. At the moment it wouldn’t make sense to qualify modules by default because of the point above - module names are too long, they need to be explicitly shortened anyway if you plan on using them in a qualified sense.
  • If you want to expose something from a module (e.g. types) but use the rest of it qualified, you need to import the module twice.

Proposed Solution

import from Control.Monad
  Reader (ReaderT(..), ask, runReaderT)
  Writer (WriterT(..), runWriterT, tell)
import from Data
  Array
  Maybe (Maybe(..), maybe, fromMaybe)
  String
  Tuple (Tuple(..))

desugars to

import Control.Monad.Reader (ReaderT(..), ask, runReaderT)
import Control.Monad.Reader as Reader
import Control.Monad.Writer (WriterT(..), runWriterT, tell)
import Control.Monad.Writer as Writer
import Data.Array as Array
import Data.Maybe (Maybe(..), maybe, fromMaybe)
import Data.Maybe as Maybe
import Data.String as String
import Data.Tuple (Tuple(..))
import Data.Tuple as Tuple

This would also allow this idea regarding making it a convention that modules in packages start with module-name-ified version of their package name much more palatable, as one may only have to write it once

import from MyModule
  SubModule1
  SubModule2
  SubModule3

instead of having something extra to tack on to the name of every import.

Potential Changes/Improvements

  • Use something different than import from, e.g. import with.
  • Use some form of brackets and separator, e.g. (,), instead of whitespace and newlines.
  • Allow an import ... as statment inside the import from statement so you can change the name of the qualified import.
  • Have some syntax for nesting, e.g.
import from Control
  Apply
  Bind
  from Monad
    Reader
    Writer
5 Likes

This looks really neat, gives me rust vibes :slight_smile:

and taking inspiration from Rust could be one way of doing this:

import Control.Monad.(
    Reader (ReaderT (..), ask, runReaderT)
  , Writer (WriterT (..), runWriterT, tell)
  )
import Data.(
    Array
  , Maybe (Maybe (..), maybe, fromMaybe)
  , String
  , Tuple (Tuple (..))
  )

mind the use of dot - compared to Rust, it may be good idea to differentiate between naming modules and their contents, otherwise we would allow for new, ambiguous situations to arise.

1 Like

I personally like the indentation syntax more, but I guess that would work too :slight_smile:

This is something that @hdgarrood has said (although I don’t have the link handy) that he would like to change when module names are made unique per package. We could dispense with all of the Control and Data prefixes, which would shorten up module names.

I’m :-1: on having modules auto-qualified using this approach. It seems too magical. I would prefer keeping it as an explicit choice. However, I would be okay with something like Rust’s self:

use std::collections::hash_map::{self, HashMap};

// We now have both `HashMap` and `hash_map` in scope.
fn bar(map1: HashMap<String, usize>, map2: hash_map::HashMap<String, usize>){}

I would also prefer to have delimiting tokens as opposed to making it based on indentation.

Maybe what I really want is just Rust use declaration semantics in PureScript? :smile:

Tbh, I like Data, Control and the other namespaces. I feel like they group modules into categories based on the type of functiinality exported.

Auto-qualification isnt magic, you literally have to specify a name if u want the module to be qualified (or so it seems. If its not then I think it should be :))

That isn’t what the initial proposal seems to be advocating for.

Having this:

import from Control.Monad
  Reader (ReaderT(..), ask, runReaderT)

desugar to this:

import Control.Monad.Reader (ReaderT(..), ask, runReaderT)
import Control.Monad.Reader as Reader

is making the as Reader implicit.

For me personally, editor import tooling has made any import madness largely disappear. I don’t care that it takes up two imports, because I didn’t write them (tooling did). I don’t care that I can’t re-export qualified modules (tooling can just import them). I don’t really care where things come from because I can just cmd+click on names on go-to the definition. All I really want is to figure out how to make imports a collapsible code region so I don’t have to ever look at them. My general recommendation is to make sure you have the tooling setup and working well enough that you don’t have to worry about it.

6 Likes

I don’t like the idea of tooling being required to have a nice experience. It could turn people off who aren’t aware of the tooling, which I would assume would be nearly every new person getting into PS. I would assume a new person looking at example code would be more likely to say “wow, no thank you” if they keep seeing giant import walls vs something more palatable. Also, certain tooling might not be available for someone’s editor of choice, and all tooling requires work to set up. I think having a great experience at the language level should be the goal.

1 Like

I’m :-1: on having modules auto-qualified using this approach. It seems too magical. I would prefer keeping it as an explicit choice

The only bad user experience one could have with auto-qualification is if someone wanted to use multiple entire modules unqualified, but it doesn’t work because it desugares to be qualified. How often do you even use an single entire module unqualified other than Prelude? It’s bad practice, seldom done, and if you really want it, you can always use the normal syntax. I just don’t see the point in having to write X as X every single time when I could just write X instead. Would you happen to have a negative UX scenario in your head that I’m not thinking of?

1 Like

Didn’t stop Java :laughing:

3 Likes

I’m just saying that there isn’t really a need to import something as qualified by default when we could add some explicit syntax that would remove the need to import the module twice.

For example, if we were able to do this:

import Control.Monad.Reader (ReaderT(..), ask, runReaderT) as Reader

This is already valid syntax and has specific semantics. It imports only those items into that pseudo-module so that you can re-export just those items.

1 Like

Oops, my bad :blush:

My point still stands: we could add some syntax to import a qualified version of a module without having to repeat it again (although I’m still not convinced this is a problem that needs solving, especially if we trend towards shorter module names).

It’s no different from JavaScript:

import * as React from 'react';
import { useState, useEffect } from 'react';
1 Like

For me personally, editor import tooling has made any import madness largely disappear.

All I really want is to figure out how to make imports a collapsible code region so I don’t have to ever look at them.

So so much in agreement with this, especially the “collapsable region” - could even be a pane or separate tab as far as i’m concerned.

1 Like