Syntax extension - adding import support to let/where bindings

Hi all, I'm sure this has come up before, but a quick bit of Googling didn't reveal any prior discussions (if you known any, let me know), so I'm kicking off a new discussion. I find myself wanting to be able to say something like: foo = ... where import Something.Specific The result would be to import the contents of Something.Specific into the scope of foo and its other where bindings, but not import into the rest of the module that foo is defined in. As a motivating example, I'm currently working on building some HTML in Haskell, and the amount of symbols that come into scope is huge, when you have a DSL for both CSS and HTML - the real pain point being that you get symbols that often conflict. Here's an example of something that doesn't type-check currently image = img [ width 50, style [ width (px 50) ] ] width here is both a symbol in the HTML DSL and the CSS DSL, but to get this to type check I need to write something like image = img [ HTML.width 50, style [ CSS.width (CSS.px 50) ] ] That's not particularly bad here, the really pain is repeatedly having to do this for every single symbol I'm using. I'd prefer to be able to write image = let css = let import CSS in [ width (px 50) ] in let import HTML in img [ width 50, style css ] This is a little bit of a contrived rewrite, but hopefully it shows you what I'd like to be able to do. Please don't fixate too much on this example, it's only intended to illustrate the idea. When defining a large amount of inline styles I think this pays off - I import once to bring all the symbols I need into scope, rather than having to qualify every symbol. In reality, it will lead to me writing code like: myDocument = html where html = div [ style containerStyle ] [ div [ style rowStyle ] "Hello" , div [ style rowStyle ] "World!" ] where import HTML containerStyle = [ backgroundColor ..., padding ..., margin ... ] where import CSS rowStyle = [ backgroundColor ..., padding ..., margin ... ] where import CSS At present the suggestion is a syntax error, so I'm hoping that part won't be too controversial. Thoughts? *ocharles*

Hello, Haskell definitely needs better namespace story, so +1. On Wed, 2015-08-05 at 11:28 +0100, Oliver Charles wrote:
Hi all,
I'm sure this has come up before, but a quick bit of Googling didn't reveal any prior discussions (if you known any, let me know), so I'm kicking off a new discussion.
Probably this blog post will be interesting for you: http://blog.haskell-exists.com/yuras/posts/namespaces-modules-qualified -imports-and-a-constant-pain.html
I find myself wanting to be able to say something like:
foo = ... where import Something.Specific
Then GHC will need to parse all the source code in order to build dependency tree. Also people may want to build dependency tree in their minds too, it is easier with global imports (in the module header.) What about the next: -- In the module header: import qualified Something.Specific as Specific ... -- Somewhere else: foo = ... where import Specific -- Note: importing by module alias Thanks, Yuras
The result would be to import the contents of Something.Specific into the scope of foo and its other where bindings, but not import into the rest of the module that foo is defined in. As a motivating example, I'm currently working on building some HTML in Haskell, and the amount of symbols that come into scope is huge, when you have a DSL for both CSS and HTML - the real pain point being that you get symbols that often conflict.
Here's an example of something that doesn't type-check currently
image = img [ width 50, style [ width (px 50) ] ]
width here is both a symbol in the HTML DSL and the CSS DSL, but to get this to type check I need to write something like
image = img [ HTML.width 50, style [ CSS.width (CSS.px 50) ] ]
That's not particularly bad here, the really pain is repeatedly having to do this for every single symbol I'm using. I'd prefer to be able to write
image = let css = let import CSS in [ width (px 50) ] in let import HTML in img [ width 50, style css ]
This is a little bit of a contrived rewrite, but hopefully it shows you what I'd like to be able to do. Please don't fixate too much on this example, it's only intended to illustrate the idea. When defining a large amount of inline styles I think this pays off - I import once to bring all the symbols I need into scope, rather than having to qualify every symbol.
In reality, it will lead to me writing code like:
myDocument = html where html = div [ style containerStyle ] [ div [ style rowStyle ] "Hello" , div [ style rowStyle ] "World!" ] where import HTML containerStyle = [ backgroundColor ..., padding ..., margin ... ] where import CSS rowStyle = [ backgroundColor ..., padding ..., margin ... ] where import CSS
At present the suggestion is a syntax error, so I'm hoping that part won't be too controversial.
Thoughts? *ocharles*

On Wed, Aug 5, 2015 at 12:34 PM Yuras Shumovich
I find myself wanting to be able to say something like:
foo = ... where import Something.Specific
Then GHC will need to parse all the source code in order to build dependency tree. Also people may want to build dependency tree in their minds too, it is easier with global imports (in the module header.) What about the next:
-- In the module header: import qualified Something.Specific as Specific ... -- Somewhere else: foo = ... where import Specific -- Note: importing by module alias
I would rather we are able to use exactly the same features as global imports to reduce the cognitive burden of knowing what you can and can't do. If those responsible for GHC tell me that what I want is unrealistic then maybe we'll have to compromise, but right now I would rather not add limitations. *ocharles*

This would be really handy to have. You could also have import "blocks", like import Foo in { ... } Where {...} is a new layout block, I guess. On Wednesday, August 5, 2015 at 9:40:00 PM UTC+10, Oliver Charles wrote:
On Wed, Aug 5, 2015 at 12:34 PM Yuras Shumovich
javascript:> wrote: I find myself wanting to be able to say something like:
foo = ... where import Something.Specific
Then GHC will need to parse all the source code in order to build dependency tree. Also people may want to build dependency tree in their minds too, it is easier with global imports (in the module header.) What about the next:
-- In the module header: import qualified Something.Specific as Specific ... -- Somewhere else: foo = ... where import Specific -- Note: importing by module alias
I would rather we are able to use exactly the same features as global imports to reduce the cognitive burden of knowing what you can and can't do. If those responsible for GHC tell me that what I want is unrealistic then maybe we'll have to compromise, but right now I would rather not add limitations.
*ocharles*

On Wed, Aug 5, 2015 at 6:28 AM, Oliver Charles
I find myself wanting to be able to say something like:
foo = ... where import Something.Specific
The result would be to import the contents of Something.Specific into the scope of foo and its other where bindings, but not import into the rest of the module that foo is defined in. As a motivating example, I'm currently working on building some HTML in Haskell, and the amount of symbols that come into scope is huge, when you have a DSL for both CSS and HTML - the real pain point being that you get symbols that often conflict.
The biggest problem with this is the question of instances. The typechecker requires that all instances be global; otherwise you can break invariants. (Consider what happens if a different Ord instance is in scope in that part of the program.) I wonder if this use case can be addressed by a different mechanism (extension), though: import qualified Something.Specific {- ... -} foo = ... where using Something.Specific -- names would be unqualified in this scope -- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net

On Wed, Aug 5, 2015 at 2:32 PM, Brandon Allbery
The biggest problem with this is the question of instances. The typechecker requires that all instances be global; otherwise you can break invariants. (Consider what happens if a different Ord instance is in scope in that part of the program.)
I wonder if this use case can be addressed by a different mechanism (extension), though:
import qualified Something.Specific {- ... -} foo = ... where using Something.Specific -- names would be unqualified in this scope
This is a good point. I hadn't considered that, but I wouldn't want to be importing instances - only symbols (types, type families, top level definitions). If we use import and rule out instance importing, then we might have to improve the error messages about missing instances. For example, No instance for (Eq Foo) In the expression: foo == foo In an equation for ‘it’: it = foo == foo Note: an instance was found in Foo, but this must be imported at the module level. Obviously the wording is open for discussion, but the idea is that GHC could be aware of the instances, find them, but refuse to use them. Questionable whether we want this, but it's an idea. using could also work, at the cost of taking up another keyword (and a potentially useful keyword, as using has meaning in other languages that we may ultimately find ourselves wanting in Haskell). *ocharles*

A couple syntactic concerns (assuming there's a sane semantics with eg typeclass instances):
- This seems essentially like name shadowing, which is a source of confusion and therefore bugs (-Wall warns about it)
- This makes me unable to see what a module imports by looking at the import statements at the top of a file
- It seems like using "H.width" and "C.width" isn't very costly
Tom
El Aug 5, 2015, a las 6:28, Oliver Charles
Hi all,
I'm sure this has come up before, but a quick bit of Googling didn't reveal any prior discussions (if you known any, let me know), so I'm kicking off a new discussion.
I find myself wanting to be able to say something like:
foo = ... where import Something.Specific
The result would be to import the contents of Something.Specific into the scope of foo and its other where bindings, but not import into the rest of the module that foo is defined in. As a motivating example, I'm currently working on building some HTML in Haskell, and the amount of symbols that come into scope is huge, when you have a DSL for both CSS and HTML - the real pain point being that you get symbols that often conflict.
Here's an example of something that doesn't type-check currently
image = img [ width 50, style [ width (px 50) ] ]
width here is both a symbol in the HTML DSL and the CSS DSL, but to get this to type check I need to write something like
image = img [ HTML.width 50, style [ CSS.width (CSS.px 50) ] ]
That's not particularly bad here, the really pain is repeatedly having to do this for every single symbol I'm using. I'd prefer to be able to write
image = let css = let import CSS in [ width (px 50) ] in let import HTML in img [ width 50, style css ]
This is a little bit of a contrived rewrite, but hopefully it shows you what I'd like to be able to do. Please don't fixate too much on this example, it's only intended to illustrate the idea. When defining a large amount of inline styles I think this pays off - I import once to bring all the symbols I need into scope, rather than having to qualify every symbol.
In reality, it will lead to me writing code like:
myDocument = html where html = div [ style containerStyle ] [ div [ style rowStyle ] "Hello" , div [ style rowStyle ] "World!" ] where import HTML containerStyle = [ backgroundColor ..., padding ..., margin ... ] where import CSS rowStyle = [ backgroundColor ..., padding ..., margin ... ] where import CSS
At present the suggestion is a syntax error, so I'm hoping that part won't be too controversial.
Thoughts? ocharles _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

On Wed, Aug 5, 2015 at 3:38 PM
A couple syntactic concerns (assuming there's a sane semantics with eg typeclass instances):
- This seems essentially like name shadowing, which is a source of confusion and therefore bugs (-Wall warns about it)
It would be the same as normal name resolution. For example, right now you can do foo = id True where id a = a foo.hs:2:9: Warning: This binding for ‘id’ shadows the existing binding imported from ‘Prelude’ at foo.hs:1:1 (and originally defined in ‘GHC.Base’) Prelude.id was introduced in a higher scope, so the definition shadows it. If you do this foo = id True where import Prelude (id) id a = a Then that should fail with "Ambiguous occurrence 'id'". If importing would shadow a top-level definition, then that should probably trigger a warning. E.g., import Prelude hiding (id) id a = a foo = id True where import Prelude (id) Would warn that the import shadows the top-level definition of id (which is the same behavior as if you declared another id in the where clauses for foo).
- This makes me unable to see what a module imports by looking at the import statements at the top of a file
Correct. But it does not make it impossible for a tool to determine what imports are available. This is also proposed as an extension, so of course if you don't want to use it, you wouldn't have to.
- It seems like using "H.width" and "C.width" isn't very costly
In practice I use a lot more than just two symbols. The point is the repeated qualification quickly introduces more noise and obscures the intent of the code. It also introduces a disconnect in the code - where did H come from? Time to jump all the way to the top of the file to find out. Of course, under my proposal it *could* be even harder to find out where H came from, but that is a decision that the (code) author decided on - and there are already many ways we *could* make Haskell unreadable. *ocharles*

In practice I use a lot more than just two symbols. The point is the repeated qualification quickly introduces more noise and obscures the intent of the code.
Well, qualification is only necessary for the symbols that conflict, right? It seems to me that if you want an EDSL with a certain prelude, you have to make sure the prelude symbols are all distinct. If you want to compose two DSLs, then you could make a third prelude that imports the other two, but renames colliding symbols. Unless there are many collisions... in which case, maybe don't define your EDSLs like that in the first place? Currently if you want to figure out all imports you parse the top of the file and can stop at the first definition. But with this feature you have to parse the whole file and thus understand all haskell grammar, including all extensions in use. I'd have to give up on my fast deps chaser and switch to slow ghc -M... which is maybe the right way anyway, I don't know. Ok, to be fair, I wouldn't, because I could choose to not use that feature, but in *theory* :) And while "you don't have to use it" is always brought up, it seems to me the more successful the feature is the more likely you do have to use it. On the other hand, lots of languages have a "local open" feature like this. I think many of them make you first import the module, and then you can "open" it in a local scope. This would address both my "parse the whole file for imports" objection and the "what about instances", because module importing would be unchanged.

On Wed, Aug 5, 2015 at 5:43 PM Evan Laforge
In practice I use a lot more than just two symbols. The point is the repeated qualification quickly introduces more noise and obscures the intent of the code.
Well, qualification is only necessary for the symbols that conflict, right? It seems to me that if you want an EDSL with a certain prelude, you have to make sure the prelude symbols are all distinct. If you want to compose two DSLs, then you could make a third prelude that imports the other two, but renames colliding symbols.
At this point I am working for the compiler (and in this case doing a lot of work!), but it should be the other way round - this makes me sad. Unless there are many collisions... in which case, maybe don't define
your EDSLs like that in the first place?
My EDSLs for HTML and CSS are meant to reflect those actual languages as closely as possible. If I start renaming things just to "fit in" with other symbols, then I've started adding burden on my users. Also, let's not forget this proposal is useful for more than just EDSLs, so lets not get too caught up on that - for example, one might wish to import Data.Text.Lazy or Data.Text in different locations depending on what they are working with. There are many packages out there with conflicting symbols that have fairly "localised" use sites, but at a granularity of a top-level definition rather than a module. Currently if you want to figure out all imports you parse the top of
the file and can stop at the first definition. But with this feature you have to parse the whole file and thus understand all haskell grammar, including all extensions in use. I'd have to give up on my fast deps chaser and switch to slow ghc -M... which is maybe the right way anyway, I don't know.
Ok, to be fair, I wouldn't, because I could choose to not use that feature, but in *theory* :) And while "you don't have to use it" is always brought up, it seems to me the more successful the feature is the more likely you do have to use it.
It makes me sad if we can't progress the language on the grounds that people's attempts at parsing the source code themselves would break. If you want to know all the imports, then we should be providing this information through tools for people to consume. On the other hand, lots of languages have a "local open" feature like
this. I think many of them make you first import the module, and then you can "open" it in a local scope. This would address both my "parse the whole file for imports" objection and the "what about instances", because module importing would be unchanged.
Indeed, this could be a path forward. I'm not really familiar with any languages that do this, could you link to some examples of how this works in other languages? *ocharles*

On Wed, Aug 5, 2015 at 9:55 AM, Oliver Charles
It makes me sad if we can't progress the language on the grounds that people's attempts at parsing the source code themselves would break. If you want to know all the imports, then we should be providing this information through tools for people to consume.
It's not whether or not there's a tool, there already is. It's that the tool must be more complicated. For example, we can get imports from haskell-src-exts but it has bugs, it can be out of date, and it's slower. Or ghc -M... which doesn't have those problems. So maybe it's not really a serious objection.
On the other hand, lots of languages have a "local open" feature like this. I think many of them make you first import the module, and then you can "open" it in a local scope. This would address both my "parse the whole file for imports" objection and the "what about instances", because module importing would be unchanged.
Indeed, this could be a path forward. I'm not really familiar with any languages that do this, could you link to some examples of how this works in other languages?
I was thinking of agda.. though it's only from memory so I could be wrong. Or perhaps it was cayenne... one of those dependently typed languages models modules as records, and then has syntax to dequalify record access. Rust has a full-on nested module system, but I seem to recall you have to declare a link dependency on an external crate ("import"), and then separately import the symbols from it ("use").

This seems to show the syntax in agda
https://code.google.com/p/agda/issues/detail?id=1077
-- M.agda ------------
open import Relation.Binary using (Setoid; module Setoid)
open import Data.Nat using (ℕ)
h : ℕ
h = 0
module M {α α=} (A : Setoid α α=)
where
open Setoid A
f : Carrier → ℕ
f _ = 0
module _ (B : Set) where
g : B → ℕ
g _ = 0
-----------------------------------------
I think the "open Setoid A" is the effect you are looking for
Alan
On Wed, Aug 5, 2015 at 7:08 PM, Evan Laforge
On Wed, Aug 5, 2015 at 9:55 AM, Oliver Charles
wrote: It makes me sad if we can't progress the language on the grounds that people's attempts at parsing the source code themselves would break. If you want to know all the imports, then we should be providing this information through tools for people to consume.
It's not whether or not there's a tool, there already is. It's that the tool must be more complicated. For example, we can get imports from haskell-src-exts but it has bugs, it can be out of date, and it's slower. Or ghc -M... which doesn't have those problems. So maybe it's not really a serious objection.
On the other hand, lots of languages have a "local open" feature like this. I think many of them make you first import the module, and then you can "open" it in a local scope. This would address both my "parse the whole file for imports" objection and the "what about instances", because module importing would be unchanged.
Indeed, this could be a path forward. I'm not really familiar with any languages that do this, could you link to some examples of how this works in other languages?
I was thinking of agda.. though it's only from memory so I could be wrong. Or perhaps it was cayenne... one of those dependently typed languages models modules as records, and then has syntax to dequalify record access. Rust has a full-on nested module system, but I seem to recall you have to declare a link dependency on an external crate ("import"), and then separately import the symbols from it ("use"). _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

On Wed, Aug 5, 2015 at 12:12 PM Alan & Kim Zimmerman
This seems to show the syntax in agda
https://code.google.com/p/agda/issues/detail?id=1077
-- M.agda ------------ open import Relation.Binary using (Setoid; module Setoid) open import Data.Nat using (ℕ)
h : ℕ h = 0
module M {α α=} (A : Setoid α α=) where open Setoid A
f : Carrier → ℕ f _ = 0
module _ (B : Set) where g : B → ℕ g _ = 0
If this is going to go into a local block of some kind, how many names are you going to use from A? How much extra work does this save over something like: import qualified Relation.Binary as Setoid ... ... where f = Setoid.f g = Setoid.g -- and whatever other names you need from Setoid. Personally, I'm not a big fan of unqualified imports - I've spent to much time trying to figure out where some variable came from in a module that just imported everything unqualified. So I'm willing to trade a little extra work now to make sure every name not from the Prelude shows up in the imports lis in order to save time every time I have to figure it out again later. I think the idea of having local imports is a win, as it means finding names is faster, as they'll be right there, instead of having to go to the imports list at the top of the page. But if I have to import the module qualified, and then pull the names I want out locally (which I will do), it's not clear that this provides a significant advantage over what we already have.

On Wed, Aug 5, 2015 at 6:08 PM Evan Laforge
On Wed, Aug 5, 2015 at 9:55 AM, Oliver Charles
wrote: It makes me sad if we can't progress the language on the grounds that people's attempts at parsing the source code themselves would break. If you want to know all the imports, then we should be providing this information through tools for people to consume.
It's not whether or not there's a tool, there already is. It's that the tool must be more complicated. For example, we can get imports from haskell-src-exts but it has bugs, it can be out of date, and it's slower. Or ghc -M... which doesn't have those problems. So maybe it's not really a serious objection.
This is a problem that shouldn't even exist. If you want a tool that supports GHC's extensions then we should be *using GHC* - it is a library after all. haskell-src has a need to exist as a non-GHC-dependent parser, and that's great - but for parsing GHC specific code we should be able to just use GHC. The fact that that isn't the case right now is a failing on our parts, but one that we seem to be actively fixing (c.f. ghc-exactprint).
On the other hand, lots of languages have a "local open" feature like
this. I think many of them make you first import the module, and then you can "open" it in a local scope. This would address both my "parse the whole file for imports" objection and the "what about instances", because module importing would be unchanged.
Indeed, this could be a path forward. I'm not really familiar with any languages that do this, could you link to some examples of how this works in other languages?
I was thinking of agda.. though it's only from memory so I could be wrong. Or perhaps it was cayenne... one of those dependently typed languages models modules as records, and then has syntax to dequalify record access. Rust has a full-on nested module system, but I seem to recall you have to declare a link dependency on an external crate ("import"), and then separately import the symbols from it ("use").
Indeed, Agda does have stuff like that - though it does require more syntax. It is a pretty sophisticated import system, though . *ocharles*

My initial attempt seems to have failed:
+1 on the idea.
I wonder if full-blown “import” is overkill for the desired effect. Many languages simply allow you to de-qualify a namespace within a smaller scope. I’m thinking of C++ at the moment: { using namespace std; … }
I think this would be preferable because it would still require that a module declare its “import dependencies” in a known place. I’m imagining chaos from large source files with several dozen import dependencies, but only a few of them defined in the “normal” place. Not to mention this would solve some of the worries about tooling, etc.
That said, there is syntactical boon from re-using the “import” keyword. Yet I don’t think it’s a stretch to make inlined imports be constrained by the module’s imports. It’s a simple compiler error: “Foo cannot be imported inline because it is not imported by the module”.
With or without the constraint, this would be an excellent feature.
Elliot
From: Haskell-Cafe [mailto:haskell-cafe-bounces@haskell.org] On Behalf Of Oliver Charles
Sent: Wednesday, August 5, 2015 12:56 PM
To: Evan Laforge
Cc: Haskell Cafe
Subject: Re: [Haskell-cafe] Syntax extension - adding import support to let/where bindings
On Wed, Aug 5, 2015 at 5:43 PM Evan Laforge
In practice I use a lot more than just two symbols. The point is the repeated qualification quickly introduces more noise and obscures the intent of the code.
Well, qualification is only necessary for the symbols that conflict, right? It seems to me that if you want an EDSL with a certain prelude, you have to make sure the prelude symbols are all distinct. If you want to compose two DSLs, then you could make a third prelude that imports the other two, but renames colliding symbols. At this point I am working for the compiler (and in this case doing a lot of work!), but it should be the other way round - this makes me sad. Unless there are many collisions... in which case, maybe don't define your EDSLs like that in the first place? My EDSLs for HTML and CSS are meant to reflect those actual languages as closely as possible. If I start renaming things just to "fit in" with other symbols, then I've started adding burden on my users. Also, let's not forget this proposal is useful for more than just EDSLs, so lets not get too caught up on that - for example, one might wish to import Data.Text.Lazy or Data.Text in different locations depending on what they are working with. There are many packages out there with conflicting symbols that have fairly "localised" use sites, but at a granularity of a top-level definition rather than a module. Currently if you want to figure out all imports you parse the top of the file and can stop at the first definition. But with this feature you have to parse the whole file and thus understand all haskell grammar, including all extensions in use. I'd have to give up on my fast deps chaser and switch to slow ghc -M... which is maybe the right way anyway, I don't know. Ok, to be fair, I wouldn't, because I could choose to not use that feature, but in *theory* :) And while "you don't have to use it" is always brought up, it seems to me the more successful the feature is the more likely you do have to use it. It makes me sad if we can't progress the language on the grounds that people's attempts at parsing the source code themselves would break. If you want to know all the imports, then we should be providing this information through tools for people to consume. On the other hand, lots of languages have a "local open" feature like this. I think many of them make you first import the module, and then you can "open" it in a local scope. This would address both my "parse the whole file for imports" objection and the "what about instances", because module importing would be unchanged. Indeed, this could be a path forward. I'm not really familiar with any languages that do this, could you link to some examples of how this works in other languages? ocharles

If you want a direct experience report, I've written a fair amount of OCaml which supports pretty much exactly this feature[1]: let open List in ... They even have a weird concise version of the syntax: List.(...) This is quite useful in practice, and doesn't seem to cause any problems. However, OCaml has different norms around external modules, so it might not translate to Haskell the same way. In OCaml, every module is automatically imported qualified. There's no list of import statements at the top. Importing ("opening") a module is equivalent to an *unqualified* import in Haskell and happens rarely. Moreover, it can appear in any part of the file. In practice this doesn't seem to be a problem, but that could be because nobody's depending on having all the imports conveniently declared up-front. Personally, I think this trade-off is perfectly acceptable: I'd rather have life be better for programmers and worse for tools than vice-versa. OCaml also doesn't have typeclasses or the open world assumption which would lead to confusing behavior one way or another. With all that in mind, OCaml's feature feels closer to Elliot's suggestion: it's more about locally *dequalifying* a module than importing it. Doing just that also fits better with Haskell's current import and typeclass system. However, I'm not sure how to design a local dequalifying statement in a way that's not confusing. [1]: http://caml.inria.fr/pub/docs/manual-ocaml-4.00/manual021.html#toc77 On Wed, Aug 5, 2015 at 10:46 AM, Elliot Cameron < elliot.cameron@covenanteyes.com> wrote:
My initial attempt seems to have failed:
+1 on the idea.
I wonder if full-blown “import” is overkill for the desired effect. Many languages simply allow you to de-qualify a namespace within a smaller scope. I’m thinking of C++ at the moment: { using namespace std; … }
I think this would be preferable because it would still require that a module declare its “import dependencies” in a known place. I’m imagining chaos from large source files with several dozen import dependencies, but only a few of them defined in the “normal” place. Not to mention this would solve some of the worries about tooling, etc.
That said, there is syntactical boon from re-using the “import” keyword. Yet I don’t think it’s a stretch to make inlined imports be constrained by the module’s imports. It’s a simple compiler error: “Foo cannot be imported inline because it is not imported by the module”.
With or without the constraint, this would be an excellent feature.
Elliot
*From:* Haskell-Cafe [mailto:haskell-cafe-bounces@haskell.org] *On Behalf Of *Oliver Charles *Sent:* Wednesday, August 5, 2015 12:56 PM *To:* Evan Laforge *Cc:* Haskell Cafe *Subject:* Re: [Haskell-cafe] Syntax extension - adding import support to let/where bindings
On Wed, Aug 5, 2015 at 5:43 PM Evan Laforge
wrote: In practice I use a lot more than just two symbols. The point is the repeated qualification quickly introduces more noise and obscures the intent of the code.
Well, qualification is only necessary for the symbols that conflict, right? It seems to me that if you want an EDSL with a certain prelude, you have to make sure the prelude symbols are all distinct. If you want to compose two DSLs, then you could make a third prelude that imports the other two, but renames colliding symbols.
At this point I am working for the compiler (and in this case doing a lot of work!), but it should be the other way round - this makes me sad.
Unless there are many collisions... in which case, maybe don't define your EDSLs like that in the first place?
My EDSLs for HTML and CSS are meant to reflect those actual languages as closely as possible. If I start renaming things just to "fit in" with other symbols, then I've started adding burden on my users.
Also, let's not forget this proposal is useful for more than just EDSLs, so lets not get too caught up on that - for example, one might wish to import Data.Text.Lazy or Data.Text in different locations depending on what they are working with. There are many packages out there with conflicting symbols that have fairly "localised" use sites, but at a granularity of a top-level definition rather than a module.
Currently if you want to figure out all imports you parse the top of the file and can stop at the first definition. But with this feature you have to parse the whole file and thus understand all haskell grammar, including all extensions in use. I'd have to give up on my fast deps chaser and switch to slow ghc -M... which is maybe the right way anyway, I don't know.
Ok, to be fair, I wouldn't, because I could choose to not use that feature, but in *theory* :) And while "you don't have to use it" is always brought up, it seems to me the more successful the feature is the more likely you do have to use it.
It makes me sad if we can't progress the language on the grounds that people's attempts at parsing the source code themselves would break. If you want to know all the imports, then we should be providing this information through tools for people to consume.
On the other hand, lots of languages have a "local open" feature like this. I think many of them make you first import the module, and then you can "open" it in a local scope. This would address both my "parse the whole file for imports" objection and the "what about instances", because module importing would be unchanged.
Indeed, this could be a path forward. I'm not really familiar with any languages that do this, could you link to some examples of how this works in other languages?
*ocharles*
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

On Wed, Aug 5, 2015 at 7:12 PM Tikhon Jelvis
If you want a direct experience report, I've written a fair amount of OCaml which supports pretty much exactly this feature[1]:
let open List in ...
They even have a weird concise version of the syntax:
List.(...)
This is quite useful in practice, and doesn't seem to cause any problems. However, OCaml has different norms around external modules, so it might not translate to Haskell the same way.
In OCaml, every module is automatically imported qualified. There's no list of import statements at the top. Importing ("opening") a module is equivalent to an *unqualified* import in Haskell and happens rarely. Moreover, it can appear in any part of the file. In practice this doesn't seem to be a problem, but that could be because nobody's depending on having all the imports conveniently declared up-front. Personally, I think this trade-off is perfectly acceptable: I'd rather have life be better for programmers and worse for tools than vice-versa.
OCaml also doesn't have typeclasses or the open world assumption which would lead to confusing behavior one way or another.
Thanks for providing another perspective! It's good to hear other languages are successfully employing this idea. With all that in mind, OCaml's feature feels closer to Elliot's suggestion:
it's more about locally *dequalifying* a module than importing it. Doing just that also fits better with Haskell's current import and typeclass system. However, I'm not sure how to design a local dequalifying statement in a way that's not confusing.
Indeed, having import stay at the top level to always bring type class instances in followed by the option of "opening" an import later is certainly more elegant and less hacky than what I'm proposing. I think this route will have to require a new keyword though, but we can establish those details later. I'm leaning more towards this idea now. Let's see what others think. http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe *ocharles*


On Thu, Aug 6, 2015 at 6:19 AM Vlatko Basic
The old Turbo Pascal keyword "with" (used for records) popped up in my mind. It's rather clear for namespace resolution and would look something like this:
with Data.ByteString $ do map f ...
with Prelude $ do map f ...
or more explicit
withImport Data.ByteString $ do ....
One problem with this is that it forces an ordering on statements. Sometimes you might want to push the imports to the side, by moving them to a where clause, rather than a let binding. I'm hoping that whatever we propose would allow you to do this. That's why I think the syntax construct needs to be something that appears at the time of doing bindings. *ocharles*

This all seems to be solidifying nicely. From my perspective, I didn't really like the original proposal, but am now in favor of where it has evolved -- with all imports declared at the top, and then adding `import ...` to the syntax of local declarations. I'm not sure where the trouble around instances comes from in this idea, though. Even if a module is imported qualified, all of its instances are available anywhere, and I don't see that changing here. As Edward said, this would just be a small change in the renamer.
Is it time to make a wiki page and post a feature request? I think so.
Thanks!
Richard
On Aug 7, 2015, at 4:26 AM, Oliver Charles
On Thu, Aug 6, 2015 at 6:19 AM Vlatko Basic
wrote: The old Turbo Pascal keyword "with" (used for records) popped up in my mind. It's rather clear for namespace resolution and would look something like this: with Data.ByteString $ do map f ...
with Prelude $ do map f ...
or more explicit
withImport Data.ByteString $ do ....
One problem with this is that it forces an ordering on statements. Sometimes you might want to push the imports to the side, by moving them to a where clause, rather than a let binding. I'm hoping that whatever we propose would allow you to do this. That's why I think the syntax construct needs to be something that appears at the time of doing bindings.
ocharles _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

On Fri, Aug 7, 2015 at 11:16 AM, Richard Eisenberg
This all seems to be solidifying nicely. From my perspective, I didn't really like the original proposal, but am now in favor of where it has evolved -- with all imports declared at the top, and then adding `import ...` to the syntax of local declarations. I'm not sure where the trouble around instances comes from in this idea, though. Even if a module is imported qualified, all of its instances are available anywhere, and I don't see that changing here. As Edward said, this would just be a small change in the renamer.
The initial request wanted the import to actually be done in in the restricted scope; keeping the actual qualified import at the top and removing the qualification in the inner scope came later. That initial version has problems for instances; the later revision does not. -- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net

If taking this route, how about using unqualified as a keyword? This also
suggests an extension - using qualified to locally "close" a globally
unqualified import.
So, something like this:
html =
let qualified Prelude
unqualified HTML
in head $ ...
This makes it clearer that what's going on is renaming rather than
importing, and avoids implicit name shadowing.
Pavel
On 5 August 2015 at 21:24, Oliver Charles
On Wed, Aug 5, 2015 at 7:12 PM Tikhon Jelvis
wrote: If you want a direct experience report, I've written a fair amount of OCaml which supports pretty much exactly this feature[1]:
let open List in ...
They even have a weird concise version of the syntax:
List.(...)
This is quite useful in practice, and doesn't seem to cause any problems. However, OCaml has different norms around external modules, so it might not translate to Haskell the same way.
In OCaml, every module is automatically imported qualified. There's no list of import statements at the top. Importing ("opening") a module is equivalent to an *unqualified* import in Haskell and happens rarely. Moreover, it can appear in any part of the file. In practice this doesn't seem to be a problem, but that could be because nobody's depending on having all the imports conveniently declared up-front. Personally, I think this trade-off is perfectly acceptable: I'd rather have life be better for programmers and worse for tools than vice-versa.
OCaml also doesn't have typeclasses or the open world assumption which would lead to confusing behavior one way or another.
Thanks for providing another perspective! It's good to hear other languages are successfully employing this idea.
With all that in mind, OCaml's feature feels closer to Elliot's
suggestion: it's more about locally *dequalifying* a module than importing it. Doing just that also fits better with Haskell's current import and typeclass system. However, I'm not sure how to design a local dequalifying statement in a way that's not confusing.
Indeed, having import stay at the top level to always bring type class instances in followed by the option of "opening" an import later is certainly more elegant and less hacky than what I'm proposing. I think this route will have to require a new keyword though, but we can establish those details later. I'm leaning more towards this idea now. Let's see what others think. http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
*ocharles*
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

Hello Oliver, This should be a relatively straightforward extension to the renamer. But like others have mentioned, you should still be required to import anything you're going to use locally at the top-level. This requirement makes it clear how instance visibility and dependency analysis should be done. Plus, you probably want this anyway: import qualified My.Qualified.DSL.CSS as CSS ... where import CSS The suggested semantics are that 'import M' is a new form of binding (so it is valid in let statements and other situations), which takes every identifier which is qualified with M and makes them available unqualified. As far as taste wise, I'm not sure how much I like or dislike this syntactic feature. But it should not be difficult to implement. Edward Excerpts from Oliver Charles's message of 2015-08-05 03:28:38 -0700:
Hi all,
I'm sure this has come up before, but a quick bit of Googling didn't reveal any prior discussions (if you known any, let me know), so I'm kicking off a new discussion.
I find myself wanting to be able to say something like:
foo = ... where import Something.Specific
The result would be to import the contents of Something.Specific into the scope of foo and its other where bindings, but not import into the rest of the module that foo is defined in. As a motivating example, I'm currently working on building some HTML in Haskell, and the amount of symbols that come into scope is huge, when you have a DSL for both CSS and HTML - the real pain point being that you get symbols that often conflict.
Here's an example of something that doesn't type-check currently
image = img [ width 50, style [ width (px 50) ] ]
width here is both a symbol in the HTML DSL and the CSS DSL, but to get this to type check I need to write something like
image = img [ HTML.width 50, style [ CSS.width (CSS.px 50) ] ]
That's not particularly bad here, the really pain is repeatedly having to do this for every single symbol I'm using. I'd prefer to be able to write
image = let css = let import CSS in [ width (px 50) ] in let import HTML in img [ width 50, style css ]
This is a little bit of a contrived rewrite, but hopefully it shows you what I'd like to be able to do. Please don't fixate too much on this example, it's only intended to illustrate the idea. When defining a large amount of inline styles I think this pays off - I import once to bring all the symbols I need into scope, rather than having to qualify every symbol.
In reality, it will lead to me writing code like:
myDocument = html where html = div [ style containerStyle ] [ div [ style rowStyle ] "Hello" , div [ style rowStyle ] "World!" ] where import HTML containerStyle = [ backgroundColor ..., padding ..., margin ... ] where import CSS rowStyle = [ backgroundColor ..., padding ..., margin ... ] where import CSS
At present the suggestion is a syntax error, so I'm hoping that part won't be too controversial.
Thoughts? *ocharles*

"Edward Z. Yang"
This should be a relatively straightforward extension to the renamer. But like others have mentioned, you should still be required to import anything you're going to use locally at the top-level. This requirement makes it clear how instance visibility and dependency analysis should be done. Plus, you probably want this anyway:
import qualified My.Qualified.DSL.CSS as CSS
... where import CSS
I'm strongly in favor of something like this proposal. Single-letter names for qualified modules seems to indicate the problem. Also, operators. Qualified operators are very ugly; unqualified operators are very confusing; explicit operator imports are tedious. So, I would love this, for example: | let import LensOperators in | (x ^%+ y <<++~ z) ^_^ k I'm often torn between unqualified and qualified imports. Qualified references are noisy and scary; unqualified references are hard to track. Block-level imports clarify references without noise. So, +1! -- Mikael Brockman

On Wed, Aug 5, 2015 at 9:29 PM Edward Z. Yang
Hello Oliver,
This should be a relatively straightforward extension to the renamer.
This is good news! Someone on Reddit mentioned something that hasn't come up here yet - what happens with Template Haskell and quasiquoters? Does the renamer fire before that stage is reached? I don't see any reason that you couldn't import TH "macros" locally, as you can any other top-level definition, unless there's a limitation in GHC. One other point that hasn't been mentioned yet, but was implicit with my original proposal of having the full power of import - should we be able to hide names in a local context? That is, should I be able to do let import Prelude hiding (div) import HTML in div "Hello, World!" My guess is that now that we're heading towards a syntax extension where we can open/unqualify a qualified import this is probably not on the table, but I thought I should raise it first before starting to work on a more formal proposal. *ocharles*

On 5/08/2015, at 10:28 pm, Oliver Charles
That's not particularly bad here, the really pain is repeatedly having to do this for every single symbol I'm using. I'd prefer to be able to write
image = let css = let import CSS in [ width (px 50) ] in let import HTML in img [ width 50, style css ]
This is very like the 'open' directive in SML. The SML version would be something like val image = let val css = let open CSS in [width (px 50)] end in let open HTML in img [width 50, style css] end end So there's prior art.
participants (16)
-
Alan & Kim Zimmerman
-
amindfv@gmail.com
-
Brandon Allbery
-
Edward Z. Yang
-
Elliot Cameron
-
Evan Laforge
-
mikael.brockman@gmail.com
-
Mike Ledger
-
Mike Meyer
-
Oliver Charles
-
Pavel Kogan
-
Richard A. O'Keefe
-
Richard Eisenberg
-
Tikhon Jelvis
-
Vlatko Basic
-
Yuras Shumovich