On Monday, 27 July 2015 at 01:59:49 UTC, Jonathan M Davis wrote:
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.
So, if I translate to regular D, here is what I get :
int foo(T)(T t) if (hasPrefix!T) {
t.prefix(); // ok
bar(t); // ok, nothign is checked
}
void bar(T)(T t) if (hasColor!T) {
t.color(); // error, color is not specified on an object of
type XXX
}
It changes nothing.
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.
We do not have to make the same limitation (especially if trait
are implicitly implemented).
Still, even with this limitation, using type is considered
superior and using unitests is not considered to be not
sufficient.
Also, your message is kind of weird as you seem to assume in that
part that what is discussed here is the same as passing argument,
when you get back to the checked exception position lower.
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.
I'm doing something similar to checked exception. Yes. I'm
passing argument down. There are similar indeed, and this is why
people though checked exception were a good idea.
The main way in which the differs is that checked Exception
expose implementation, while typed argument provide a contract
for the caller.
Consider it that way. Template are a meta language within the
language. With template you write code that write code.
At this meta level, types are values. Instantiating a template is
the same as calling a function (function that will generate
code). That is the reason why static if works so well, and that
is the reason why static foreach is asked for. This is the reason
why Andrei's approach to present compile time arguments in TDPL
rather than templates works.
For some reason, unitests cannot replace types for code that
connect to database, database code itself, code that render
pages, code that do scientific computation, code that do
rendering, code that crunches numbers, code that do GUI, code for
command line utilities, code that do whatever, but for code that
wirte code, yeah, they trully do the trick !
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/lsxidsyweczhojouc...@forum.dlang.org
- Jonathan M Davis
Will read that soon.