Rant after trying Rust a bit
Jonathan M Davis via Digitalmars-d
digitalmars-d at puremagic.com
Sun Jul 26 18:59:47 PDT 2015
On Sunday, 26 July 2015 at 22:59:09 UTC, deadalnix wrote:
> On Sunday, 26 July 2015 at 03:42:22 UTC, Walter Bright wrote:
>> On 7/25/2015 3:28 PM, deadalnix wrote:
>>> Also, argument from ignorance is hard to maintain when the
>>> thread is an actual
>>> feedback from experience.
>>
>> You say that interfaces do the same thing. So please show how
>> it's done with the example I gave:
>>
>> int foo(T: hasPrefix)(T t) {
>> t.prefix(); // ok
>> bar(t); // error, hasColor was not specified for
>> T
>> }
>>
>> void bar(T: hasColor)(T t) {
>> t.color();
>> }
>
> I'm not sure what is the problem here.
The problem here is that if you're dealing with traits or
concepts or whatever that are essentially interfaces, and foo
uses the Prefix interface/trait/concept and then wants to use bar
which has the Color interface/trait/concept, it has to then
require that what it's given implements both the Prefix and Color
interfaces/traits/concepts.
In the case of actual interfaces, this really doesn't work well,
because you're forced to basically have an interface that's
derived from both - e.g. PrefixAndColor. Otherwise, you'd be
forced to do nonsense like have foo take a Prefix and then cast
it to Color to pass to bar and throw an exception if the type it
was given didn't actually implement both interfaces. And it's
downright ugly. In reality, the code would likely simply end up
not being that generic and would require some specific type that
you were using in your code which implemented both Prefix and
Color, and foo just wouldn't work with anything else, making it
far less reusable. So, with actual interfaces, it becomes very
difficult to write generic code.
With traits or concepts, presumably, you could say that foo
required a type that implemented both Prefix and Color, which
fixes the problem of how you're able to accept something that
takes both generically without coming up with something like
PrefixAndColor (though if you can only list one trait/concept as
required, you're just as screwed as you are with interfaces). But
even if you can list multiple traits/concepts as required by a
function, you quickly end up with a proliferation of
traits/concepts that need to be covered by a higher level
function like foo, because not only would foo need to list all of
the traits/concepts that the functions it uses directly require,
but it has to list all of the traits/concepts that it even
indirectly requires (potentially from far down the call stack).
So, any change to a function that even gets called indirectly
could break foo, because it didn't have the right traits/concepts
listed in its requirements. And all of the functions in the call
chain would have to have their list of required traits/concepts
updated any time there was any tweak to any of the underlying
functions, even if those functions would have actually worked
fine with most of the code that was calling them, because the
types being passed in had the new requirements already (it's just
that the functions higher up the stack didn't list the updated
requirements yet).
By having foo list all of the traits/concepts that would be
required anywhere in its call stack, you're doing something very
similar to what happens with checked exceptions where the
exceptions have to be listed clear up the chain. It's not quite
the same, and there's no equivalent to "throws Exception" (since
that would be equivalent to somehow having a trait/concept that
said that you didn't care what the type given to foo
implemented). Rather, you're basically being forced to list each
trait/concept individually up the chain - but it's still a
similar problem to checked exceptions. It doesn't scale well. And
if a function is required to list all of the traits/concepts that
are required - even indirectly - then changing the requirements
of a function - even slightly - results in code breakage similar
to that of checked exceptions when you change which exceptions a
function throws, and "throws Exception" wasn't being used. And
even if you're not worried about breaking other people's code,
it's a maintenance problem to maintain that list clear up the
chain.
Unfortunately, we _do_ have a similar problem with template
constraints if we insist on putting all of the function's
requirements in its template constraint rather than just its
immediate requirements. But at least with template constraints,
if the top-level constraint is missing something that a function
somewhere deeper in the stack requires, then you get a
compilation error only when the type being passed in doesn't pass
the constraint on the function deeper in the stack. So, if you
adjust a template constraint, it will only break code that
doesn't work with the new constraint - even code that uses that
function indirectly (possibly even quite deeply in a call stack,
far from their own code) won't break due to the change, unless
the type being used doesn't pass the new constraint. And when it
does fail, the errors may not be pretty, but they do tell you
exactly what's required to figure out what's wrong when you look
at the source code. Whereas the traits/concepts solution would
break _all_ code that used the function that was adjusted (even
indirectly), not just the code that wouldn't work with the new
requirements.
I discussed this quite a bit more elsewhere in this thread:
http://forum.dlang.org/post/lsxidsyweczhojoucnsw@forum.dlang.org
- Jonathan M Davis
More information about the Digitalmars-d
mailing list