Fun with range deprecations
Fra via Digitalmars-d
digitalmars-d at puremagic.com
Tue Aug 12 01:20:09 PDT 2014
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?
More information about the Digitalmars-d
mailing list