On Monday, 11 August 2014 at 22:59:34 UTC, H. S. Teoh via Digitalmars-d wrote:
So, a recent Phobos deprecation introduced a fun regression:

        https://issues.dlang.org/show_bug.cgi?id=13257

While the bug was filed specifically for the use case
range.splitter.map, the problem is actually much more general, and far
from obvious how to address.

First, let's consider the following setup. Start with this example
range:

        struct MyRange(T) {
                // input range
                @property bool empty() { ... }
                @property T front() { ... }
                void popFront() { ... }

                // forward range
                @property MyRange save() { ... }

                // bidirectional range
                @property T back() { ... }
                void popBack() { ... }
        }

Suppose we write an algorithm that operates on ranges in general, including MyRange. The current convention is to forward as many range methods as possible, so that if you give it a bidi range, it will, where
possible, give you a wrapped bidirectional range. So we do this:

        auto myAlgo(R)(R range)
                if (isInputRange!R)
        {
                struct Result {
                        // input range methods
                        @property bool empty() { ... }
                        @property U front() { ... }
                        void popFront() { ... }

                        // N. B. if we got a forward range, and we can
                        // implement .save, then do so.
                        static if (isForwardRange!R &&
                                canDoForwardRange)
                        {
                                @property Result save() { ... }
                        }

                        // N. B. if we got a bidirectional range, and we
                        // can implement .back and .popBack, then do so.
                        static if (isBidirectionalRange!R &&
                                canDoBidirectionalRange)
                        {
                                @property U back() { ... }
                                void popBack() { ... }
                        }
                }
                return Result(...);
        }

Now suppose, for whatever reason, we decide that implement MyRange as a bidirectional range was a bad idea, and we decide to deprecate it:

        struct MyRange(T) {
                ... // input & forward range API here

deprecated("Use of MyRange as bidirectional range is now deprecated")
                {
                        @property T back() { ... }
                        void popBack() { ... }
                }
        }

Now the fun begins. Currently, isBidirectionalRange uses
is(typeof(...)) to check if .back and .popBack exist in target type. So when myAlgo() checks MyRange, this check passes, because even though .back and .popBack have been deprecated, they obviously still exist, so they do have a valid type. So that check passes. Therefore, myAlgo()
will try to export a bidirectional interface in its result.

However, inside Result.back and Result.popBack, when it actually tries to use MyRange.back and MyRange.popBack, the compiler throws up its
hands and say, Wait!! .back and .popBack are deprecated!!

Note that this happens *regardless* of whether .back and .popBack are actually used by the user code that calls myAlgo(). This means that if user code only ever uses forward range capabilities of myAlgo(), users will *still* get irrelevant deprecation messages -- for bidirectional
range features that they never use! Furthermore, the only reason
myAlgo() added .back and .popBack which trigger the deprecation
messages, is because the is(typeof(...)) check tests positive for
bidirectional range support.

This problem, obviously, is not specific to bidirectional ranges, or, for that matter, *any* range based code. It's a general problem with
wrapped types, of the form:

        struct Wrapped {
                deprecated void method();
        }

        struct Wrapper(T) {
                T t;
                static if (is(typeof(t.method())))
                {
                        void func() {
                                t.method();
                        }
                }
        }

The problem here is that t.method() is generic, and it has specifically tested for the usability of t.method(), yet when it tries to actually use t.method(), it hits a deprecation message. How is it supposed to
know if t.method() has been deprecated? Currently we have no
__traits(deprecated) test. And even if we did, this may not be the best solution (are we going to now insert __traits(deprecated) all over generic code, on the off-chance that some random user type somewhere
will get deprecated in the unforeseeable future?).

So the question now is: how do we deal with this issue? Currently, this problem already exists in Phobos 2.067a, and random user code that calls
splitter(...).map(...) will hit this problem.

So far the following have been suggested, none of which seem
particularly satisfactory:

1) Make Wrapper.func() a template function, so that the deprecation message is not triggered unless the user actually calls it (in which case the deprecation message *should* be triggered). The problem is that when the deprecation message *is* triggered, it comes from deep inside Phobos, and users may complain, why did you export a bidirectional range
API if that support is already deprecated?

2) Add a __traits(deprecated) or is(T == deprecated) test, and update all affected sig constraints in Phobos to check for this. Doesn't sound
like an appealing solution.

3) Have std.algorithm.map specifically check for a specific overload of std.algorithm.splitter, and omit .back and .popBack in that one case. Very hackish, solves the immediate problem, but leaves the general problem unsolved. User code that wraps around splitter in a similar way
to map will *still* be broken (even if they never actually use
bidirectional features).

4) Shorten the deprecation cycle of splitter. Doesn't even solve the immediate problem, just shortens it, and still leaves the general problem unsolved. User code that wraps around splitter is still broken
(even if they never actually use bidirectional features).

Since no obvious acceptable solution seems forthcoming, I thought we should bring this to the forum for discussion to see if anyone has a
better idea.


T
Noob question... shouldn't __traits(compile) enable us to handle the situation correctly?

Reply via email to