Remove ways of importing except `import exposing (..)`?

280 views
Skip to first unread message

Will White

unread,
Aug 18, 2016, 9:33:25 AM8/18/16
to Elm Discuss
import ModuleWithToString

toString typeFromThatModule

Compiles with unexpected results that you have to catch.

import ModuleWithToString exposing (..)

toString typeFromThatModule

Doesn't compile: "use of toString is ambiguous: did you mean Basics.toString or ModuleWithToString.toString?"

Peter Damoc

unread,
Aug 18, 2016, 10:11:21 AM8/18/16
to Elm Discuss
"Qualified imports are preferred."
http://elm-lang.org/docs/syntax#modules

I try to avoid as much as possible importing everything from a module. 
If I would have IDE support for automatic imports I would never do a import Module exposing (..)

Will White

unread,
Aug 18, 2016, 10:37:37 AM8/18/16
to Elm Discuss
I prefer safe code to qualified imports.

Janis Voigtländer

unread,
Aug 18, 2016, 10:40:21 AM8/18/16
to elm-d...@googlegroups.com
In what ways are qualified imports at odds with safe code?

I think the opposite is the case: unqualified imports lead to less code safety. 
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Will White

unread,
Aug 18, 2016, 10:48:09 AM8/18/16
to Elm Discuss
You have to remember to qualify.

Janis Voigtländer

unread,
Aug 18, 2016, 10:56:50 AM8/18/16
to elm-d...@googlegroups.com
That's not an answer to my question I understand as a problem description. 

Also, there have been earlier threads here that have discussed what can go wrong if you unwittingly import with exposing everything. So, where not remembering that one of those imports brought a certain function name into scope, lead to unexpected results. If your concern is valid, theirs is at least as much. 

Will White

unread,
Aug 18, 2016, 10:57:41 AM8/18/16
to Elm Discuss
(Safe code being code where you don't have to remember to do things.)

Will White

unread,
Aug 18, 2016, 11:05:18 AM8/18/16
to Elm Discuss
What unexpected results? The compiler has your back if two unqualified functions have the same name.

Will White

unread,
Aug 18, 2016, 11:08:01 AM8/18/16
to Elm Discuss
What unexpected results? The compiler has your back if two unqualified functions have the same name.

Will White

unread,
Aug 18, 2016, 11:15:20 AM8/18/16
to Elm Discuss
Perhaps I should have said "I prefer not having to remember to do things to qualified imports." I am genuinely interested to know what unexpected results others have had by import exposing (..), because at this point I'm always doing that (in order to avoid the problem I stated at the top).

Janis Voigtländer

unread,
Aug 18, 2016, 11:18:56 AM8/18/16
to elm-d...@googlegroups.com

I can’t search for the thread right now, but I’m sure you can find it yourself via the archive. In any case, one aspect of it (but I think there were more) was this: https://github.com/elm-lang/elm-make/issues/61


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Nick H

unread,
Aug 18, 2016, 12:08:29 PM8/18/16
to elm-d...@googlegroups.com
If you prefer not having to remember things, then you should use qualified imports.

Stripping the namespace off of all your imported functions means that your code no longer tells you where these functions come from. You will have to remember this information yourself. This doesn't seem like much of a mental burden at the moment, but it will likely become much more burdensome in 6 months when you are reading through your code again (e.g. "Now, which of these 10 modules did that 'reticulateSplines' function come from?") And if you never run into that annoyance, I guarantee that every other person who reads your code will.

What's more, APIs that follow the design guidelines are going to make your life even harder, because the functions will have names designed to work with a qualifier. Look at your own example. If "toString" only works on one type, then it is a terribly undescriptive name. But if the module is named "Foo" and the type defined in that module is named "Foo", then "Foo.toString" is not a terrible name.

From the design guidelines:

A function called State.runState is redundant and silly. More importantly, it encourages people to use import State exposing (..) which does not scale well. In files with many so-called "unqualified" dependencies, it is essentially impossible to figure out where functions are coming from. This can make large code bases impossible to understand, especially if custom infix operators are used as well. Repeating the module name actively encourages this kind of unreadable code.

With a name like State.run the user is encouraged to disambiguate functions with namespacing, leading to a codebase that will be clearer to people reading the project for the first time. A great example from the standard library is Bitwise.and

Will White

unread,
Aug 18, 2016, 12:12:21 PM8/18/16
to Elm Discuss
I've searched for "import exposing (..)" but I couldn't find the thread. I'll carry on with import exposing (..), but I'll be sure (i.e. I'll have to remember!) to post back here if I encounter a problem.

Will White

unread,
Aug 18, 2016, 12:37:20 PM8/18/16
to Elm Discuss
I can still namespace functions with import exposing (..).

Nick H

unread,
Aug 18, 2016, 1:03:57 PM8/18/16
to elm-d...@googlegroups.com
Is that a good idea? It seems like occasionally, but not always, using the namespace would make your code less readable than not doing it at all.


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Janis Voigtländer

unread,
Aug 18, 2016, 1:35:19 PM8/18/16
to elm-d...@googlegroups.com
More importantly, maybe, you will have to *remember* to use each imported function at least once in qualified form in each module, or you will end up in the situation Nick described, where in six months you don't know (and other readers of your code do not even know the next day after you wrote the code) where reticulateSplines comes from. 

Will White

unread,
Aug 18, 2016, 3:19:13 PM8/18/16
to Elm Discuss
Then I'll always namespace them. If I forget one though...

Janis Voigtländer

unread,
Aug 18, 2016, 3:31:53 PM8/18/16
to elm-d...@googlegroups.com
You won't be able to forget one if "exposing (..)" is eliminated from the language, as is proposed in the issue I linked to.

You'd then either have to qualify or add the entities explicitly in "exposing (concrete entities)". Problem solved, because in both cases a reader of the code does not need to wonder too much where any given name comes from. 

Janis Voigtländer

unread,
Aug 18, 2016, 3:41:02 PM8/18/16
to elm-d...@googlegroups.com
Or maybe I'm still misunderstanding what your exact problem is in the absence of "exposing (..)". Maybe you can expand on the problem description from your first message in this thread?

Will White

unread,
Aug 18, 2016, 5:15:04 PM8/18/16
to Elm Discuss
In the first snippet, I was erroneously expecting toString to call toString from ModuleWithToString, possibly because I'd just been looking at toString in ModuleWithToString. import exposing (..) makes this foolproof.

Joey Eremondi

unread,
Aug 18, 2016, 5:23:57 PM8/18/16
to elm-d...@googlegroups.com
Okay, so I think this is somewhat of a special case. The problems you are running into come because toString is provided by Basics, so you expected your import to shadow it, but it did not.

This is a bit of a tricky scenario:
    * The typechecker can't help us, because Basics.toString has type (a -> String) i.e. polymorphic in its argument, so it will always typecheck
    * The whole point of Basics.toString is to be a quick and dirty debugging tool, so needing to import it is a hassle

When we use Foo.map, generally the typechecker will tell us if we used List.map when we wanted Maybe.map. So we shouldn't use polymorphic values with the same name when we don't have to.

Solutions:
  * Rename Basics.toString, maybe to Basics.showAnything, or something similar
  * Move Basics.toString to Debug.toString, and import Debug qualified by default.

Non-solutions:
  * Import everything unqualified by default, shadowing Basics



To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Will White

unread,
Aug 18, 2016, 6:26:45 PM8/18/16
to Elm Discuss
Without import exposing (..) by default, it'll be the same problem if there are two functions with the same name from two modules, and either they both have the same type signature, or one of them is polymorphic in its argument.

> import Module1WithToX exposing (toX)

> import Module2WithToX

> toX "r"

"rr" : String


> import Module1WithToX exposing (..)

> import Module2WithToX exposing (..)

> toX "r"

-- NAMING ERROR ---------------------------------------------- repl-temp-000.elm


This usage of variable `toX` is ambiguous.


5|   toX "r"

    ^^^

Maybe you want one of the following?


    Module1WithToX.toX

    Module2WithToX.toX

Joey Eremondi

unread,
Aug 18, 2016, 6:39:25 PM8/18/16
to elm-d...@googlegroups.com
@Will White: this case is excedingly rare. For example, it's not possible to write "toString : a -> String" in Pure Elm. It needs Native code to do so. There are theorems that say that any function of the type (a -> T) for some concrete type T have to ignore their argument. The key is that toString isn't polymorphic in its return type.

There won't be (a -> JSON) or (a -> Dict Int String) or (a -> Html Msg) functions floating around. String is a special case, where we break the rules for practical debugging.

So yes, it happens any time that there's a function polymorphic in its argument *and not in its return type*, but this is basically only with toString. It's not like every module exports its own version of (\ x y -> x).

The case you describe is possible, and still awkward even if the compiler does catch your mistake. But the solution is *just don't import Module1WithToX exposing (..)*. Do Module1WithToX.toX. Your code will be clearer, and it will not be ambiguous which one you mean.


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Ian Mackenzie

unread,
Aug 18, 2016, 7:28:56 PM8/18/16
to Elm Discuss
It seems to me that the issues you are encountering come from two things:
  • Using a mix of qualified and unqualified imports
  • Having a function the same name as one in Basics
One solution is to never use qualified imports, but a better solution is to always use qualified imports and just not name any of your own functions toString (or min, or max, or degrees, or anything else in Basics). That way you never have any ambiguity between functions, you never have to switch between qualified and unqualified (if, perhaps, you start using Maybe.map in a file that already uses List.map), and it's always clear where a function (or value) is coming from.

Max Goldstein

unread,
Aug 18, 2016, 8:27:01 PM8/18/16
to Elm Discuss
I won't go so far as to say that (..) imports should be removed from the language but I'm definitely on the side of rarely using them for reasons that have been cited repeatedly in this thread. Furthermore, having written several data structures in Elm, you want to keep names like map available for you to define yourself. So if you see map with no qualifier, it's the map for this data structure, defined in this module.

Ian Mackenzie

unread,
Aug 18, 2016, 8:31:10 PM8/18/16
to Elm Discuss
Edit: I did some searching after I posted this and realized just how common it was to have custom toString functions, so I take back what I said about not naming your own functions toString. Switching Basics.toString to Debug.toString could be a good solution, although that would be annoying for simple cases like converting an Int to a String (which is often needed in non-debug situations like setting up HTML/SVG attributes). Maybe there should be a specialized Int.toString function instead. I do think it's a sufficiently special case that 'always import qualified' is still the best strategy though.

Will White

unread,
Aug 19, 2016, 4:13:45 AM8/19/16
to Elm Discuss
It being rare means you're less likely to spot it when it happens.

Do you namespace all your Html, for example?

Will White

unread,
Aug 19, 2016, 7:27:29 AM8/19/16
to Elm Discuss
Namespacing everything actually makes import redundant. No more imports if you like to namespace everything. Then make import exposing (..) the new import. Well up for that!


On Thursday, August 18, 2016 at 2:33:25 PM UTC+1, Will White wrote:
import ModuleWithToString

toString typeFromThatModule

Compiles with unexpected results that you have to catch.

import ModuleWithToString exposing (..)

toString typeFromThatModule

Doesn't compile: "use of toString is ambiguous: did you mean Basics.toString or ModuleWithToString.toString?"

Janis Voigtländer

unread,
Aug 19, 2016, 7:46:14 AM8/19/16
to elm-d...@googlegroups.com

Okay, I see the usability problem now. But I don’t think it calls for always importing with exposing (..). Maybe it does call for a warning to be issued in such situations by the compiler or by an optional code quality tool.


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Will White

unread,
Aug 19, 2016, 12:13:36 PM8/19/16
to Elm Discuss
It's worth saying that this issue will become more frequent as Elm's package number grows, not to mention more dangerous as Elm is used in more places. I don't think this should be handled by anything more optional than the compiler.
Reply all
Reply to author
Forward
0 new messages