Is there any benefit in always using whitelisted imports?

I’ve seen codebases that strictly only import the symbols that a particular file needs via whitelisting, instead of just importing the full module.

So for example:

import DA.List (head)

instead of

import DA.List

Are there any benefits from doing it like this (eg. compilation performance), aside from making ambiguous references less likely?

3 Likes

The main benefit imho is readability. All you need to figure out where a symbol was imported from is a search function. You can argue that your IDE makes that redundant but that’s ignores how much time developers spend reading code outside of their IDE (did you ever review a PR on github? :slightly_smiling_face:). A middleground between doing this for all imports and not doing it at all is to do it for uncommon modules. Things like DA.List or most other things from daml-stdlib are quite common so most developers are familiar with it and the explicit import does not provide much benefit. For other things, e.g., let’s say you are using modules from FinLib, the explicit import might be useful.

There is also the option of using qualified imports to get similar benefits. As for most things wtr to codestyle there is no right or wrong answer here. Use whatever you or perhaps more importantly your team prefer and define a code style guide if you expect everyone to stick to it.

I wouldn’t expect any noticeable difference in compilation performance. It might make name resolution slightly faster but ignoring artificial edge cases that is never your bottleneck.

5 Likes

My personal favorite reason is “keeping the local scope clean”. This isn’t really any different in DAML versus any other language with specific vs wildcard imports, except that wildcard imports are safer in DAML compared to many other languages, some of which will just “pick one” if there is a collision. I don’t think it’s wise to have any kind of rule or “coding standard”; just use whatever looks best in each situation.

There is, however, a reason specific to Haskell and DAML for which it may be more interesting to use symbol imports, particularly when using an external library, or a library in a separate part of your current project: it’s common for libraries to define their symbols in lots of separate modules, but to use names unique across the whole project, then re-export every module in a single project-wide module.

So, learning about lens, if you wish to use ^. you know you can simply write import Control.Lens ((^.)). You don’t have to know where in the “hierarchy of lens types” ^. lies in order to just grab it and use it. However, writing it in this form makes it very easy to come back later and say "hey, I am only using symbols from Control.Lens.Getter, let’s make that explicit with import Control.Lens.Getter ((^.))". The explicit symbol imports make it clearer which partition of the “uber-import” you are actually using.

Built-in understanding of what re-exports are in Haskell and DAML means that you can perform this refactoring as piecemeal as you wish; this is not typically possible for other libraries languages whose facilities make “uber vs a la carte” imports an exclusive choice. That increases the value of doing symbol imports in such situations in DAML further.

3 Likes