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.

Reply via email to