Proposal for an extension: in-module namespaces

Hello I've been thinking about an extension to GHC which would allow creating new module-like namespaces within normal Haskell modules. Because this kind of namespacing system seems quite obvious to me, I ask if it has already been proposed or discussed in detail? The motivation: 1. It would assist in solving the 'pollution of namespace' -problem, of which many Haskellers have complained. 2. It would allow building bundles of related modules which can be exported from one module and then imported into others. This could result in radically fewer import statements. Rant: I'm tired of writing import qualified Data.ByteSstring as B import qualified Data.ByteString.Builder as B import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Builder as BL import qualified Data.HashMap.Strict as HM import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as BL A huge portion of this appears in almos all my Haskell source files. If I wrote my code in Python, I could condense all these imports to: So, you see, it was zero lines of code. I'm also tired of these patterns: (personName p), (companyName c), (objectName o), ... and I hardly prefer (name (p :: Person)), (name (c :: Company)) or (name (o ::Object)) using the DuplicateRecordFields syntax. Proposal: I propose that modules can contain named submodules. They can be exported and imported just like anything else within a module, like this: {-# LANGUAGE Submodules #-} module Library.Things (qualified module Person, qualified module Company, personWorksInCompany ) where -- import ... qualified module Person (Person (..)) where -- This is the module Library.Things.Person. Submodules could possibly -- use some other keywords than 'qualified module'. Maybe just 'module', -- 'submodule' or 'namespace'. data Person = Person { name :: {-# UNPACK #-} !T.Text, id :: {-# UNPACK #-} !Unique, ... } -- Back in Library.Things qualified module Company where data Company = Company { name :: {-# UNPACK #-} !T.Text, employees :: {-# UNPACK #-} !V.Vector Unique, ... } -- back in Library.Things again: personWorksIn :: Person.Person -> Company.Company -> Bool personWorksIn p c = Person.id p `elem` Company.employees c -- ... end of Library.Things And in another file: {-# LANGUAGE Submodules #-} -- The following demonstrates some ways of importing submodules. They are -- not meant to be used like this in real code: import Library.Things import qualified Library.Things as Things -- Now the submodules are also in scope and can be referred to like this: person1 :: Person.Person person2 :: Things.Person.Person company :: Library.Things.Company.Company -- And we also could have written: after importing Library.Things: import Person -- And now data type Person, selector name, etc. are in scope -- If we also imported the Company module: import Things.Company -- selector 'name' would now be ambiguous and need qualification at use site. -- Import lists would also work when bringing submodule contents to current -- namespace. Like this: import Library.Things hiding (module Company) -- or import qualified Library.Things (module Person) -- Which would allow Library.THings.Person.Person to be used requiring use of -- the fully qualified name. -- But the following would not work if Library.Things was not already -- imported: import Library.Things.Person -- because there is no such module, just submodule. -- ... end of other file Some projects probably would like to create a module like this: module Imports (qualified module B, qualified module HM, qualified module T ) where import Data.ByteString as B -- ... and so forth ... Open questions: - Should there be a separate keyword for importing submodules, for example 'use' or 'import namespace'? - Would imports be allowed within submodules? I'd say no for importing other modules, but importing submodules could be ok. - Would it be possible to extend submodules after their initial definition by just writing a new 'qualified module' block? I'd say yes, because submodules are just about namespacing. It is today possible to import many modules qualified and put them in the same namespace, like is often done. - What do you fellow Haskellers think about this all? -- Aura

My subjective cents:
- Namespace pollution practically doesn't exist if you import qualified.
- The common stuff you always import can just go to a custom prelude. You
can roll your own based on, say, Protolude [1].
Please weight a power-to-disturbance ratio of new features. Each of these
become additional burden when learning or for tool writers (say automatic
refactoring tools etc).
Robin
[1]: http://www.stephendiehl.com/posts/protolude.html
2017-03-27 23:11 GMT+02:00 Aura Kelloniemi
Hello
I've been thinking about an extension to GHC which would allow creating new module-like namespaces within normal Haskell modules. Because this kind of namespacing system seems quite obvious to me, I ask if it has already been proposed or discussed in detail?
The motivation: 1. It would assist in solving the 'pollution of namespace' -problem, of which many Haskellers have complained.
2. It would allow building bundles of related modules which can be exported from one module and then imported into others. This could result in radically fewer import statements.
Rant:
I'm tired of writing
import qualified Data.ByteSstring as B import qualified Data.ByteString.Builder as B import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Builder as BL import qualified Data.HashMap.Strict as HM import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as BL
A huge portion of this appears in almos all my Haskell source files. If I wrote my code in Python, I could condense all these imports to:
So, you see, it was zero lines of code.
I'm also tired of these patterns: (personName p), (companyName c), (objectName o), ... and I hardly prefer (name (p :: Person)), (name (c :: Company)) or (name (o ::Object)) using the DuplicateRecordFields syntax.
Proposal:
I propose that modules can contain named submodules. They can be exported and imported just like anything else within a module, like this:
{-# LANGUAGE Submodules #-} module Library.Things (qualified module Person, qualified module Company, personWorksInCompany ) where
-- import ...
qualified module Person (Person (..)) where -- This is the module Library.Things.Person. Submodules could possibly -- use some other keywords than 'qualified module'. Maybe just 'module', -- 'submodule' or 'namespace'. data Person = Person { name :: {-# UNPACK #-} !T.Text, id :: {-# UNPACK #-} !Unique, ... }
-- Back in Library.Things qualified module Company where data Company = Company { name :: {-# UNPACK #-} !T.Text, employees :: {-# UNPACK #-} !V.Vector Unique, ... }
-- back in Library.Things again:
personWorksIn :: Person.Person -> Company.Company -> Bool personWorksIn p c = Person.id p `elem` Company.employees c
-- ... end of Library.Things
And in another file:
{-# LANGUAGE Submodules #-}
-- The following demonstrates some ways of importing submodules. They are -- not meant to be used like this in real code: import Library.Things import qualified Library.Things as Things
-- Now the submodules are also in scope and can be referred to like this: person1 :: Person.Person person2 :: Things.Person.Person company :: Library.Things.Company.Company
-- And we also could have written: after importing Library.Things: import Person -- And now data type Person, selector name, etc. are in scope -- If we also imported the Company module: import Things.Company -- selector 'name' would now be ambiguous and need qualification at use site.
-- Import lists would also work when bringing submodule contents to current -- namespace. Like this: import Library.Things hiding (module Company) -- or import qualified Library.Things (module Person) -- Which would allow Library.THings.Person.Person to be used requiring use of -- the fully qualified name.
-- But the following would not work if Library.Things was not already -- imported: import Library.Things.Person -- because there is no such module, just submodule.
-- ... end of other file
Some projects probably would like to create a module like this:
module Imports (qualified module B, qualified module HM, qualified module T ) where
import Data.ByteString as B -- ... and so forth ...
Open questions:
- Should there be a separate keyword for importing submodules, for example 'use' or 'import namespace'? - Would imports be allowed within submodules? I'd say no for importing other modules, but importing submodules could be ok. - Would it be possible to extend submodules after their initial definition by just writing a new 'qualified module' block? I'd say yes, because submodules are just about namespacing. It is today possible to import many modules qualified and put them in the same namespace, like is often done. - What do you fellow Haskellers think about this all?
-- Aura _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

Hello,
Something like that was proposed multiple times, e.g. here:
https://ghc.haskell.org/trac/ghc/wiki/Records/NestedModules
I personally like the idea a lot because it makes module system much more
refactoring friendly. E.g. you can merge two modules into one file without
renaming conflicting names as a step in a long refactoring chain. Or you
can prototype in one file, and split the code into multiple modules later.
Also, I think backpack has some limited support for multiple modules in one
file. At least I saw something similar in backpack examples in Edward
Yang's blog.
Anyway I'd like to see your idea officially proposed via ghc-proposals.
Thanks,
Yuras.
28 мар 2017 г. 0:14 пользователь "Aura Kelloniemi"

Excerpts from Yuras Shumovich's message of 2017-03-28 12:23:36 +0300:
Also, I think backpack has some limited support for multiple modules in one file. At least I saw something similar in backpack examples in Edward Yang's blog.
You can put multiple modules in a bkp file, but that is mostly a tool for compiler tests, and not intended to be used for "real" software. Edward

28-03-2017, Аўт а 09:22 -0400, Edward Z. Yang напісаў:
Excerpts from Yuras Shumovich's message of 2017-03-28 12:23:36 +0300:
Also, I think backpack has some limited support for multiple modules in one file. At least I saw something similar in backpack examples in Edward Yang's blog.
You can put multiple modules in a bkp file, but that is mostly a tool for compiler tests, and not intended to be used for "real" software.
Thank you for clarification. Yeah, I think I saw it on GHC Trac too. It is very neat! But what prevents it from being useful for real software? I often use anansi ( http://hackage.haskell.org/package/anansi ) when prototyping new projects, so that I can write multiple modules in one file. (One .anansi file generates a number of .hs files.) But it is an ugly workaround at best. Yuras.
Edward

There are a number of problems: 1. There is no support for mixed Backpack/regular module projects, and it's not altogether clear how you would implement it, since it's not obvious from file naming where a "module" might live. This is in contrast to something like anansi which generates hs files so that the existing lookup codepaths work. 2. Recompilation avoidance operates on an entire bkp file, so if you modify any submodule, you have to rebuild everything. Not great if you actually have a lot of code. 3. GHCi doesn't know how to deal with bkp files at all. bkp files also support defining multiple libraries, which is a phase violation with Cabal, and not really solvable at all. Edward Excerpts from Yuras Shumovich's message of 2017-03-28 16:43:54 +0300:
28-03-2017, Аўт а 09:22 -0400, Edward Z. Yang напісаў:
Excerpts from Yuras Shumovich's message of 2017-03-28 12:23:36 +0300:
Also, I think backpack has some limited support for multiple modules in one file. At least I saw something similar in backpack examples in Edward Yang's blog.
You can put multiple modules in a bkp file, but that is mostly a tool for compiler tests, and not intended to be used for "real" software.
Thank you for clarification. Yeah, I think I saw it on GHC Trac too. It is very neat! But what prevents it from being useful for real software? I often use anansi ( http://hackage.haskell.org/package/anansi ) when prototyping new projects, so that I can write multiple modules in one file. (One .anansi file generates a number of .hs files.) But it is an ugly workaround at best.
Yuras.
Edward

Hi, Am Dienstag, den 28.03.2017, 00:11 +0300 schrieb Aura Kelloniemi:
- What do you fellow Haskellers think about this all?
no opinion yet, but with my context fixes proposal at https://github.com/ghc-proposals/ghc-proposals/pull/40 I got the feedback that on its own, it does not pull its weight, but as part of a larger deal, it might be a useful feature. So I guess you can also consider if “parametrized modules” might be something that you would want to add to your proposal. Greetings, Joachim -- Joachim “nomeata” Breitner mail@joachim-breitner.de • https://www.joachim-breitner.de/ XMPP: nomeata@joachim-breitner.de • OpenPGP-Key: 0xF0FBF51F Debian Developer: nomeata@debian.org

I've been thinking about an extension to GHC which would allow creating new module-like namespaces within normal Haskell modules. Because this kind of namespacing system seems quite obvious to me, I ask if it has already been proposed or discussed in detail?
Take a look at Agda's module system as described in the user documentation[1] and Norell's PhD thesis[2]. It supports the features you propose, along with lightweight parameterised modules (subsuming Joachim's "context fixes" proposal) and some additional stuff. At the same time, it doesn't buy into the complexity of ML-style generative module functors, which I think is a good tradeoff. [1] http://agda.readthedocs.io/en/v2.5.2/language/module-system.html [2] http://www.cse.chalmers.se/~ulfn/papers/thesis.pdf (PDF) Cheers, Jannis
participants (6)
-
Aura Kelloniemi
-
Edward Z. Yang
-
Jannis Limperg
-
Joachim Breitner
-
Robin Palotai
-
Yuras Shumovich