On 20/12/13 10:06, Meta wrote:
2 comes with its own problems, though. With imports at the top, you can easily
tell what the module imports at a glance. Putting imports into the deepest
possible scopes where they are used will make it a huge chore to hunt all the
imports down. You can of course grep for "import" (or ctrl+f for more plebeian
editors), but you still don't have that singular list in one place that you can
easily look at to tell what the module imports.

Conversely, one problem of imports at the top is that it's easy for unnecessary imports to hang around just because you don't have a direct association between the import and what it's actually used for.

I think the real issue we face is that it's not possible to really gain the advantages of deeply-nested-as-possible imports without _also_ breaking up the modules; we can't have Andrei's strategy (2) without some of strategy (3). Example: std.algorithm.topN, the only part of std.algorithm (apart from unittests) that requires std.random:

    void topN(alias less = "a < b",
            SwapStrategy ss = SwapStrategy.unstable,
            Range)(Range r, size_t nth)
        if (isRandomAccessRange!(Range) && hasLength!Range)
    {
        static assert(ss == SwapStrategy.unstable,
                "Stable topN not yet implemented");
        while (r.length > nth)
        {
            auto pivot = uniform(0, r.length);
            // ... etc. ...
        }
    }

Now, at first glance it's easy to just insert an extra line before "auto pivot = ...":

    import std.random : uniform;

... but it quickly becomes non-trivial if you want to do what really ought to be an option here, and allow a non-default RNG to be passed to the function:

    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
            && isUniformRNG!RandomGen)  // <--- needs std.random.isUniformRNG
    {
        static assert(ss == SwapStrategy.unstable,
                "Stable topN not yet implemented");
        while (r.length > nth)
        {
            auto pivot = uniform(0, r.length, rng);
            // ... etc. ...
        }
    }

    // New function to support old 2-parameter version using default RNG
    void topN(alias less = "a < b",
            SwapStrategy ss = SwapStrategy.unstable,
            Range)(Range r, size_t nth)
        if (isRandomAccessRange!(Range) && hasLength!Range)
    {
        topN(r, ss, rndGen);    <---- needs std.random.rndGen;
    }

The second of the two needed imports is easy and can be nested inside the function, but the first -- the isUniformRNG template -- requires the import in the module's own scope.

So, to really allow std.algorithm to do away with its dependency on std.random, you need to break isUniformRNG and similar templates away from the rest of the std.random functionality.

(Yes, I know, you could break topN out from the rest of std.random, but remember that topN itself is only dependent on std.random for the case where you're using the unstable swap strategy.)

I'd hypothesize that probably it would be productive to start modularizing Phobos modules by separating out all the various helper templates like isUniformRNG, after which it should be much easier to avoid needing top-level module imports.

Reply via email to