July 24, 2015
On Fri, Jul 24, 2015 at 11:29:28AM -0700, Walter Bright via Digitalmars-d wrote: [...]
> Overloading with constraints is commonplace in Phobos. Haven't really had any trouble with it.
[...]

Actually, I've had trouble with overloading with constraints. Unlike C++, D does not allow multiple possible template instantiations in overload sets. While this is ostensibly a good thing, it makes writing fallbacks extremely cumbersome.

For example, suppose I have a set of overloads of myFunc(), each of which specifies some set of constraints defining which subset of types they are implemented for:

	void myFunc(T)(T t) if (someSetOfConditions!T) { ... }
	void myFunc(T)(T t) if (someOtherSetOfConditions!T) { ... }
	void myFunc(T)(T t) if (yetAnotherSetOfConditions!T) { ... }

As long as the conditions are mutually exclusive, everything is OK.

But suppose these conditions describe specializations of the function for specific concrete types (e.g. I want to take advantage of extra properties of the concrete types to implement faster algorithms), but I want a fallback function containing a generic implementation that works for all types. Unfortunately, I can't just write another overload of myFunc without constraints, because it causes conflicts with the preceding overloads. The only way to achieve this is to explicitly negate every condition in all other overloads:

	// generic fallback
	void myFunc(T)(T t)
		if (!someSetOfConditions!T &&
		    !someOtherSetOfConditions!T &&
		    !yetAnotherSetOfConditions!T) { ... }

This isn't too bad at first glance -- a little extra typing never hurt nobody, right? Unfortunately, this doesn't work if, say, all these overloads are part of some module M, but the user wishes to extend the functionality of myFunc by providing his own specialization for his own user-defined type, say, in a different module. That is prohibited because the generic myFunc above doesn't have the negation of whatever conditions the user placed in his specialization, so it will cause an overload conflict.

It is also a maintenance issue that whenever somebody adds or removes a new specialization of myFunc (even within module M), the sig constraints of the generic fallback must be updated accordingly.

It gets worse if the original sig constraints were buggy -- then you have to fix them in both the specialization and the generic fallback -- and hope you didn't miss any conditions (e.g. a typo causes the generic fallback not to pick up something that the specialization now declines).

If you think this is a contrived scenario, you should take a look at std.conv, where this particular problem has become a maintenance headache. Among the several dozens of overloads of toImpl, there are all kinds of sig constraints that, at first glance, isn't obvious whether or not they cover all the necessary cases, and whether various fallbacks (yes there are multiple! the above scenario is a simplified description) correctly catch all the cases they ought to catch. When there is a bug in one of the toImpl overloads, it's a nightmare to find out which one it is -- because you have to parse and evaluate all the sig constraints of every overload just to locate the offending function.

Maybe as a Phobos *user* you perceive that overloading with sig constraints is nice and clean... But as someone who was foolhardy enough once to attempt to sort out the tangled mess that is the sig constraints of toImpl overloads, I'm getting a rather different perception of the situation.


T

-- 
What do you mean the Internet isn't filled with subliminal messages? What about all those buttons marked "submit"??
July 24, 2015
On 7/24/2015 11:35 AM, Jacob Carlborg wrote:
> On 2015-07-24 08:42, Walter Bright wrote:
>
>> It's a good question. And the answer is, the top level function does not
>> list every interface used by the call tree. Nested function calls test
>> at runtime if a particular interface is supported by an object, using
>> dynamic casting or QueryInterface() calls. It's fundamentally different
>> from traits and concepts.
>
> If you have an interface and then doing a dynamic cast then you're doing it
> wrong. Yes, I know that there are code that uses this, yes I have done that too.

Dynamic cast is no different from QueryInterface(), which is how it's done, and the reason is the point of all this - avoiding needing to enumerate every interface needed by the leaves at the root of the call tree.

July 24, 2015
On 7/24/2015 11:42 AM, Jacob Carlborg wrote:
> I don't see the difference compared to a regular parameter. If you don't specify
> any constraints/traits/whatever it like using "Object" for all your parameter
> types in Java.

So constraints then will be an all-or-nothing proposition? I believe that would make them essentially useless.

I suspect I am not getting across the essential point. If I have a call tree, and at the bottom I add a call to interface X, then I have to add a constraint that additionally specifies X on each function up the call tree to the root. That is antiethical to writing generic code, and will prove to be more of a nuisance than an asset.

Exactly what sunk Exception Specifications.
July 24, 2015
On 07/24/2015 08:30 PM, Walter Bright wrote:
> On 7/24/2015 7:50 AM, jmh530 wrote:
>> I'm a little confused here. I seem to be of the belief that D's
>> interfaces can
>> accomplish virtually the same thing as Rust's traits. In your example,
>> if the
>> type you pass to foo also inherits from hasColor, then it shouldn't be
>> a problem.
>
> As I replied earlier,
>
> "It's a good question. And the answer is, the top level function does
> not list every interface used by the call tree. Nested function calls
> test at runtime if a particular interface is supported by an object,
> using dynamic casting or QueryInterface() calls. It's fundamentally
> different from traits and concepts."
>

This kind of testing for conformance can easily be allowed for traits/concepts at compile time. (E.g. by default, or add a special Meta trait.)
July 24, 2015
On 7/24/2015 11:39 AM, Jacob Carlborg wrote:
> Perhaps it might be good idea to allow to set a predefined version identifier,
> i.e. set "linux" on Windows just to see that it compiles. Think of it like the
> "debug" statement can be used as an escape hatch for pure functions.


I don't want to encourage "if it compiles, ship it!" I've strongly disagreed with the C++ concepts folks on that issue, and they've downvoted me to hell on it, too :-)

I get the impression that I'm the only one who thinks exclusive traits is more of a problem than a solution. It's deja vu all over again with Exception Specifications. So, one of:

1. I'm dead wrong.
2. I fail to explain my argument properly (not the first time that's happened, fer sure).
3. People strongly want to believe in traits.
4. Smart people say it tastes great and is less filling, so there's a bandwagon effect.
5. The concepts/traits people have done a fantastic job convincing people that the emperor is wearing the latest fashion :-)

It's also clear that traits work very well "in the small", i.e. in specifications of the feature, presentation slide decks, tutorials, etc. Just like Exception Specifications did. It's the complex hierarchies where it fell apart.
July 24, 2015
On 7/24/2015 11:40 AM, Jacob Carlborg wrote:
> On 2015-07-23 22:46, Walter Bright wrote:
>
>> like implicit declaration of variables.
>
> I would say that Ruby is pretty far up the list of successful languages, a lot
> higher than D ;)

I know you're a great fan of Ruby, so I'll bite my tongue :-)

July 24, 2015
On 07/23/2015 10:09 PM, Walter Bright wrote:
>
>
> Now, if you want to disallow { } within the embedded if statement, then
> the proposal becomes nothing more than:
>
>      ? => if
>      : => else

This is obviously not true:

a?b:c => a if b else c // ..?


>
> which is a potayto potahto thing.

Some people have trouble with the precedence of ?: .

Fun fact: D and C++ have different precedence for ?: . :-)
July 24, 2015
On 07/24/2015 08:56 AM, Walter Bright wrote:
> On 7/23/2015 10:49 PM, Tobias Müller wrote:
>> Walter Bright <newshound2@digitalmars.com> wrote:
>>> I know a lot of the programming community is sold on exclusive
>>> constraints (C++ concepts, Rust traits) rather than inclusive ones (D
>>> constraints). What I don't see is a lot of experience actually using
>>> them
>>> long term. They may not turn out so well, like ES.
>>
>> Haskell has type classes since ~1990.
>
> Haskell is sometimes described as a bondage-and-discipline language.
> Google it if you don't believe me :-) Such languages have their place
> and adherents, but I don't think D is directed that way.
>

Also, if there are carrots in your meal, it is vegetarian.

> Exception Specifications were proposed for Java and C++ by smart,
> experienced programmers. It looked great on paper, and in the simple
> examples in the proposals. The unfit nature of it only emerged years
> later. Concepts and traits appear to me to suffer from the same fault.
>

They are not the same thing. Not even close.
July 24, 2015
On Friday, 24 July 2015 at 19:26:33 UTC, Walter Bright wrote:
>
> 1. I'm dead wrong.
> 2. I fail to explain my argument properly (not the first time that's happened, fer sure).


I wouldn't be surprised if you're right, contra one, and you've explained it properly, contra two, but I don't understand anyway. I don't have a problem deferring to people more knowledgeable than I am.
July 24, 2015
On Friday, 24 July 2015 at 13:22:34 UTC, Jacob Carlborg wrote:
> On 2015-07-24 02:55, Tofu Ninja wrote:
>
>> I think I agree on the if else issue, seems arbitrary as we already have
>> ?:. Other statements as expressions have less obvious meanings. The only
>> part is that I wish you could have blocks as expressions. The thing is
>> with ufcs, it really should be possible.
>>
>> For example the following does not compile:
>> int a = {return 4;};
>>
>> but the following does:
>> int a = {return 4;}();
>>
>> I know it's a really small difference, but with UFCS, I would expect you
>> the be able to omit the () and have the function literal called
>> automatically. Though I can see that this would have problems with auto
>> and knowing if it should be a function pointer or to call the function.
>>
>> I guess what I would expect is "auto a = {return 4;};" to type a to a
>> function pointer, but if you explicitly type a to int then the literal
>> should be called.
>>
>> Does UFCS even apply to function pointers? I guess it is a problem, it
>> does not seem to be obvious when to call and when to copy the pointer. I
>> don't really know what should happen. I think I read a dip a little
>> while ago that might have addressed this, but I don't really remember. I
>> dont know, now that I have written this, it seems to have more problems
>> than I originally thought.
>
> How does UFCS apply here? There isn't even a dot in the code.

Is omitting the () not part of ufcs? Or does it have some other name, I can never remember.