Changes between Version 3 and Version 4 of ShorterImportSyntax

Sep 16, 2015 4:40:12 PM (4 years ago)



  • ShorterImportSyntax

    v3 v4  
    55Trac ticket is #10478.
    7 Link to discussions on
    8  * Haskell cafe discussion thread
    9  * Analysis of public code to characterize how common the confusing case would be months ago.
     7== Motivation ==
     9There are several styles of importing identifiers from other modules in use in Haskell today. To review, unqualified imports of the form,
     12import Data.Map
     15bring into scope every identifier exported by the `Data.Map` module. This means that names such as `Map` and `member` can be used without any additional ceremony. However, this `import` statement also brings into scope identifiers such as `null` that, if used, will conflict with the `null` implicitly imported from `Prelude`.
     17To avoid such conflicts, one may write an import such that a subset of the names exported by the target module (in this case `Data.Map`) are brought into scope.
     20import Data.Map (Map, member)
     23But now if the programmer does wish to refer to the `null` exported by `Data.Map`, they must write `Data.Map.null`. This is a cumbersome name, so there is a way of providing an alias for the `Data.Map` portion of that fully qualified name.
     26import qualified Data.Map as M
     29Now the programmer may write `M.Map`, `M.member`, and `M.null` to refer to those names exported by the `Data.Map` module. But they may no longer simply write `Map` to refer the name exported by `Data.Map`. If the `Map` name is used often, the noise of the `M.` prefixes can be a nuisance. The annoyance of these prefixes is felt perhaps more acutely when attempting to use operators, particularly those that include a dot in their names!
     31To distinguish those names the programmer wishes to frequently write from those they are willing to burden with additional syntax, a common idiom is to import a module ''twice''.
     34import Data.Map (Map, member)
     35import qualified Data.Map as M
     38Now, the programmer may write `Map` to refer to `Data.Map.Map`, and `M.null` to refer to `Data.Map.null`.
     40This pattern applies to common modules such as `Data.Map`, `Data.Set`, `Data.Sequence`, `Data.Text`, `Data.Vector`, etc.
     42== A Modest Improvement ==
     44Without changing the semantics of any existing code, we propose to support the syntax,
     47import Data.Map (Map, member) as M
     50to achieve the exact same result as the last example of the previous section.
     52The grammar of this new import form is,
     55impdecl -> import modid impspec as modid
     58In this notation, `import` and `as` are keywords; `modid` is a module name; and `impspec` is a parenthesized list of identifiers that may optionally be prefixed by the word `hiding` (e.g. `import Data.Map hiding (null)`).
     60This is illegal syntax in Haskell2010.
     62== The Long View ==
     64There is a faction of programmers who favor the exclusive use of either explicit or qualified imports, as in the two-line import of `Data.Map` shown above. This style has the advantage that a reader may easily determine where an identifier comes from without consulting anything other than the source file at hand. Revisiting that example two-line import,
     67import Data.Map (Map)
     68import qualified Data.Map as M
     71a reader encountering the identifier `Map` may look to the import section of the current file to see it explicitly listed as coming from the `Data.Map` module. Another reader may encounter `M.null` and know that this is unlikely to be the standard `Prelude.null` thanks to the qualification prefix. A consultation with the import section of the current file reveals what the `M.` prefix refers to, and all mysteries are solved.
     73Other than using two lines and a fair bit of repetition, the aesthetics of this import style have proved divisive within the community, leading to solutions that insert white space between the `import` keyword and the module name if the `qualified` keyword is not present. This is done for the sake of aligning module names in the import section to aid in the reading of that section. More specifically, imports are often sorted by module name, and having the sorted column begin at different textual columns on different lines is arguably a wart. An example of this indentation solution would be,
     76import           Data.Map (Map)
     77import qualified Data.Map as M
     80The proposed syntax subsumes the vast majority of import scenarios, and may be used in a style that completely omits the `qualified` keyword.
     83import Data.Map (Map) as M
     84import Data.Set (Set) as S
     85import Data.Text (Text) as T
     88These sorted imports require no additional work to bring the sorted column into alignment, and cut the number of characters used for imports by half.
    1190== Specification ==
     92With the `ShortImports` language extension, all imports are of the following form,
    17 Semantics are the same as existing imports unless both an `impspec` (i.e. a parenthesised list of identifiers optionally prefixed with the `hiding` keyword) and an `as` (e.g. `as M`) are present. In such a case, all identifiers exported by `modid` are accessible behind the prefix given by the `as`.
    19 In the language spec,
     98We can break down uses (note that the first three are unchanged from Haskell2010; also note that all existing uses of the `qualified` keyword are unchanged),
    22 impdecl →       import [qualified] modid [as modid] [impspec]
     101import Data.Map
     102import Data.Map (Map)
     103import Data.Map hiding (null)
     104import Data.Map (Map) as M
     105import Data.Map hiding (null) as M
    25 This grammar leaves out things like package imports and source imports, so I'm not sure if you want me to provide one grammar for all of them. For ShortImports, the grammar is,
     108* The first brings into scope everything exported by `Data.Map`.
     109* The second brings into scope the identifier `Map`. Everything else exported by `Data.Map` may be referenced by writing its full name (e.g. `Data.Map.null`).
     110* The third brings into scope everything exported by `Data.Map` ''except'' the `null` identifier. Thus, `Map` is in scope, but `Data.Map.null` must be written out in its entirety.
     111* The fourth is new syntax. It brings into scope `Map`. Everything else exported by `Data.Map` may be referenced by writing its full name, or with the alias `M` replacing the `Data.Map` portion of its full name (e.g. `M.null`).
     112* The fifth is also new syntax. It brings into scope everything exported by `Data.Map` ''except'' the `null` identifier. Thus, `Map` is in scope, but `null` does not refer to `Data.Map.null`. However, the programmer may write `M.null` to refer to `Data.Map.null`.
     114== For Students of Haskell ==
     116Having many ways to do the same thing can be a point of confusion for those learning a language. In a `ShortImports` world, one may encourage something like the following breakdown of `import` syntax to handle the vast majority of uses:
     118An `import` statement is of the form,
    27 impdecl -> import modid impspec as modid
     120import ModuleName [(unqualifiedImports) [as Alias]]
    30 == Examples ==
     123If the statement stops at the module name, everything is brought into scope. If it continues, the next part is a list of names to be brought into scope. After that, an alias for the module may be given in order to use shorter qualified names.
    32 ...
     125== Potential Confusion ==
     127There is an existing import form that looks similar to the proposed syntax,
     130import Data.Map as M
     133The syntactic distinction is in whether or not there is an `impspec` between the first `modid` and the `as` keyword. This is a subtle difference, but fortunately the syntax without the `impspec` is not in common use in publicly available code. An analysis of the Stackage Nightly package set on June 3, 2015 revealed that 0.3% of all `import` statements use this syntax.
     135With this style of `import`, the identifier `null`, for example is ambiguous. To resolve the ambiguity, the programmer must write `M.null` to refer to `Data.Map.null` or `Prelude.null` to refer to the identifier implicitly imported from `Prelude`. In contrast, when a qualified import is used, the bare `null` identifier continues to refer to `Prelude.null`.