December 21, 2013
On Friday, 20 December 2013 at 19:40:22 UTC, Michel Fortin wrote:
> Actually, "static import" already exists. And semantically it's pretty much the same thing as the above: you have to use the symbol's full name. But currently the compiler will import eagerly. I doubt there'd be any breakage if "static" changed to mean "lazily imported".

Just a joke =)
December 21, 2013
21-Dec-2013 00:43, Martin Nowak пишет:
> On 12/20/2013 06:27 PM, Andrei Alexandrescu wrote:
>>
>> I had this idea fot a while, and Walter is favorable of it as well -
>> extend "import" for one-shot use. With that feature the example would
>> become:
>>
>>      void topN(alias less = "a < b",
>>              SwapStrategy ss = SwapStrategy.unstable,
>>              Range, RandomGen)(Range r, size_t nth, ref RandomGen rng)
>>          if (isRandomAccessRange!(Range) && hasLength!Range
>>              && import.std.random.isUniformRNG!RandomGen)
>>      { ... }
>>
>> In this case "import" would syntactically be placed at the beginning of
>> a qualified name, meaning "import this module lazily and look up the
>> symbol in it".
>>
>> This would simplify quite a lot of two-liners into one-liners in other
>> places, too.
>
> The fact that template constraints use the module scope is indeed a root
> cause for a lot of module dependencies, so we should tackle this problem
> specifically.
>
> Couldn't static imports be made lazy without breaking any code?
> The above example would read.
>
> static import std.range.
>
> void foo(R)(R range) if (std.range.isForwardRange!R)
> {
> }
>
That has the disadvantage of importing the whole std.range.
I seriously doubt that we'd get anything better then:

> import std.range.traits;
>
> void foo(R)(R range) if (isForwardRange!R)
> {
       import std.range;
...
> }

Stays practical w.r.t. cutting down dependencies and no need to uglify constraints. They are not that readable already.

-- 
Dmitry Olshansky
December 21, 2013
On Saturday, 21 December 2013 at 08:08:42 UTC, Dmitry Olshansky wrote:
> That has the disadvantage of importing the whole std.range.
> I seriously doubt that we'd get anything better then:
>
> > import std.range.traits;
> >
> > void foo(R)(R range) if (isForwardRange!R)
> > {
>        import std.range;
> ...
> > }
>
> Stays practical w.r.t. cutting down dependencies and no need to uglify constraints. They are not that readable already.

I agree, to an extent. That's definitely the ultimate solution that should be taken. Smaller modules are better in general.

That said, supporting a lazy static import feature might not be a bad idea. Then a hybrid approach could be taken, which would help things in the short-run. Maybe doing something like this:

    static import std.range;
    alias isForwardRange = std.range.isForwardRange;

    void foo(R)(R range) if (isForwardRange!R)
    {
        ...
    }

That would be readable now and would support easy changes to the better packaged approach later. I'd think this would be something possible to do in 2.066 whereas splitting up everything will likely take several versions.
December 21, 2013
On Saturday, 21 December 2013 at 08:08:42 UTC, Dmitry Olshansky wrote:
> That has the disadvantage of importing the whole std.range.
> I seriously doubt that we'd get anything better then:
>
> > import std.range.traits;
> >
> > void foo(R)(R range) if (isForwardRange!R)
> > {
>        import std.range;
> ...
> > }
>
> Stays practical w.r.t. cutting down dependencies and no need to uglify constraints. They are not that readable already.

I think it was mostly agreed here that we should do both for best result.
December 21, 2013
On 12/21/13 12:08 AM, Dmitry Olshansky wrote:
> 21-Dec-2013 00:43, Martin Nowak пишет:
>> On 12/20/2013 06:27 PM, Andrei Alexandrescu wrote:
>>>
>>> I had this idea fot a while, and Walter is favorable of it as well -
>>> extend "import" for one-shot use. With that feature the example would
>>> become:
>>>
>>>      void topN(alias less = "a < b",
>>>              SwapStrategy ss = SwapStrategy.unstable,
>>>              Range, RandomGen)(Range r, size_t nth, ref RandomGen rng)
>>>          if (isRandomAccessRange!(Range) && hasLength!Range
>>>              && import.std.random.isUniformRNG!RandomGen)
>>>      { ... }
>>>
>>> In this case "import" would syntactically be placed at the beginning of
>>> a qualified name, meaning "import this module lazily and look up the
>>> symbol in it".
>>>
>>> This would simplify quite a lot of two-liners into one-liners in other
>>> places, too.
>>
>> The fact that template constraints use the module scope is indeed a root
>> cause for a lot of module dependencies, so we should tackle this problem
>> specifically.
>>
>> Couldn't static imports be made lazy without breaking any code?
>> The above example would read.
>>
>> static import std.range.
>>
>> void foo(R)(R range) if (std.range.isForwardRange!R)
>> {
>> }
>>
> That has the disadvantage of importing the whole std.range.

Yah but only if the symbol foo is actually used.

Andrei

December 21, 2013
21-Dec-2013 21:10, Andrei Alexandrescu пишет:
> On 12/21/13 12:08 AM, Dmitry Olshansky wrote:
>> 21-Dec-2013 00:43, Martin Nowak пишет:

>>> Couldn't static imports be made lazy without breaking any code?
>>> The above example would read.
>>>
>>> static import std.range.
>>>
>>> void foo(R)(R range) if (std.range.isForwardRange!R)
>>> {
>>> }
>>>
>> That has the disadvantage of importing the whole std.range.
>
> Yah but only if the symbol foo is actually used.

That assuming static import becomes lazy (if/when).

In such a case I'd be against the idom still if only because of extra verbosity in constraints - it's a place where we'd want to have less of it.

Second point is that even if import becomes lazy it's still analyze-the-whole-module at the first reference required.

No escaping the fact that both constraints and bodies of templates function need to use fine grained imports (more . The more can be shifted inside of the body, and the more specific it gets the better dependency management we have. The latter implies the well-known benefit of having less stuff to analyze, compile and link.

Thus I conclude that introducing lazy loading of symbols
accomplishes too little for the amount of changes it entails.

-- 
Dmitry Olshansky
December 21, 2013
On 12/21/13 1:06 PM, Dmitry Olshansky wrote:
> 21-Dec-2013 21:10, Andrei Alexandrescu пишет:
>> On 12/21/13 12:08 AM, Dmitry Olshansky wrote:
>>> 21-Dec-2013 00:43, Martin Nowak пишет:
>
>>>> Couldn't static imports be made lazy without breaking any code?
>>>> The above example would read.
>>>>
>>>> static import std.range.
>>>>
>>>> void foo(R)(R range) if (std.range.isForwardRange!R)
>>>> {
>>>> }
>>>>
>>> That has the disadvantage of importing the whole std.range.
>>
>> Yah but only if the symbol foo is actually used.
>
> That assuming static import becomes lazy (if/when).
>
> In such a case I'd be against the idom still if only because of extra
> verbosity in constraints - it's a place where we'd want to have less of it.

That's why I'm saying: make all imports lazy!!!!


Andrei
December 21, 2013
On Saturday, 21 December 2013 at 21:16:23 UTC, Andrei Alexandrescu wrote:
> That's why I'm saying: make all imports lazy!!!!

How? It has been already mentioned in this thread that this does not seem possible, at least withing existing language.
December 21, 2013
22-Dec-2013 01:16, Andrei Alexandrescu пишет:
> On 12/21/13 1:06 PM, Dmitry Olshansky wrote:
>> 21-Dec-2013 21:10, Andrei Alexandrescu пишет:
>>> On 12/21/13 12:08 AM, Dmitry Olshansky wrote:
>>>> 21-Dec-2013 00:43, Martin Nowak пишет:
>>
>>>>> Couldn't static imports be made lazy without breaking any code?
>>>>> The above example would read.
>>>>>
>>>>> static import std.range.
>>>>>
>>>>> void foo(R)(R range) if (std.range.isForwardRange!R)
>>>>> {
>>>>> }
>>>>>
>>>> That has the disadvantage of importing the whole std.range.
>>>
>>> Yah but only if the symbol foo is actually used.
>>
>> That assuming static import becomes lazy (if/when).
>>
>> In such a case I'd be against the idom still if only because of extra
>> verbosity in constraints - it's a place where we'd want to have less
>> of it.
>
> That's why I'm saying: make all imports lazy!!!!

Unless language defines a way to tell apart and split off a group of declarations inside of a module as independent block laziness doesn't help any. The whole reason is to avoid analyzing the whole module and pulling in its globals. If lazy import can pull only pieces (per symbol dependencies) of module that are actually required - cool, but it's seems very distant possibility.

As it stands the only thing lazy buys us is "pay as you touch" contrary to "pay as you name the intent to touch". The problem is that the payment is for the whole stock of the said "shop". I see second problem (granularity of imports) as far more critical then the first (condition under which the pieces are imported). The second problem seems solvable within the current implementation, the first seems like it would need arbitrary amount of time to fix and gains are marginal.



-- 
Dmitry Olshansky
December 21, 2013
On 12/21/13, Dicebot <public@dicebot.lv> wrote:
> On Saturday, 21 December 2013 at 21:16:23 UTC, Andrei Alexandrescu wrote:
>> That's why I'm saying: make all imports lazy!!!!
>
> How? It has been already mentioned in this thread that this does not seem possible, at least withing existing language.

The first step is to avoid reading the imports at all *unless* there's symbols missing. And then try to match selective imports first if there are any missing symbols. To demonstrate, here's the first test-case:

-----
module test;

import std.stdio : writeln;
import std.algorithm;

void main()
{
}
-----

The compiler *does not* need to import any other modules (other than object.d, of course), because it doesn't find any missing symbols referenced from "test.d".

Test-case 2:
-----
import std.stdio : writeln;
import std.algorithm;

void main()
{
    writeln("");
}
-----

The compiler matches the missing symbol with the selective import "writeln", so it knows it only has to load std.stdio, *but not* std.algorithm.

Test-case 3:
-----
import std.stdio : writeln;
import std.algorithm : map;
import std.range;  // might be here (compiler doesn't know)
import std.container;  // or here

void main()
{
    "foo".front;
}
-----

The compiler tries to find a selective import "front", but it doesn't find it. What's important here is that it still does not have to load std.stdio or std.algorithm, as we're explicitly loading only a set of symbols which were never referenced from the test.d module.

So the next step here is for the compiler to try and load each module in sequence (probably via the declaration order, first std.container), and if there's a match the compiler would stop loading other modules (no need to load std.container if std.range has "front").

-----

I could think of more optimizations, for example if we had a way of exporting a list of module-level symbols into some kind of intermediary format (say JSON), the compiler could look up this list rather than to have to eagerly load every module in search of a symbol. For example:

Test-case 4:
-----
import std.stdio;
import std.algorithm;
import std.range;
import std.container;

void main()
{
    "foo".front;
}
-----

If we had a "symbols.json", it might list things like: "front": { std.range, std.stdio }, so the compiler would know only to look for this symbol in these modules (and /if/ they are actually imported in the test.d module).