Rant after trying Rust a bit

deadalnix via Digitalmars-d digitalmars-d at puremagic.com
Mon Jul 27 12:53:05 PDT 2015


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/lsxidsyweczhojoucnsw@forum.dlang.org
>
> - Jonathan M Davis

Will read that soon.


More information about the Digitalmars-d mailing list