July 24, 2015
On Friday, 24 July 2015 at 00:55:35 UTC, Tofu Ninja wrote:
> On Thursday, 23 July 2015 at 20:09:34 UTC, Walter Bright wrote:
>> On 7/23/2015 7:49 AM, ixid wrote:
>>> If we had a clean sheet wouldn't it be better to have if return a value and
>>> ditch ternary?
>>
>> Then we'd start seeing code like:
>>
>>     x = 45 + if (y == 10) { while (i--) z += call(i); z; } else { switch (x) { case 6: foo(); y; } + tan(z);
>>
>> I.e. the embedding of arbitrary statements within expressions. We already have some of this with embedded anonymous lambda support, and I've discovered one needs to be very careful in formatting it to not wind up with an awful unreadable mess.
>>
>> So I'd be really reluctant to continue down that path.
>>
>> Now, if you want to disallow { } within the embedded if statement, then the proposal becomes nothing more than:
>>
>>     ? => if
>>     : => else
>>
>> which is a potayto potahto thing.
>>
>> I agree that trivial syntax issues actually do matter, but having used ?: a lot, I have a hard time seeing embeddable if-else as a real improvement, in fact I find it more than a little jarring to see.
>
> 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.

Actually now that I think about it, I think I would expect auto a = { return 4;}; to type a to an int and call the function literal, and auto a = &{ return 4;}; to type a to a function pointer. I think that makes sense. Then if a is a function pointer auto b = a; would type b to a function pointer as well. I suppose UFCS really does not make sense to function pointers, but does make sense for function literals.

I expect {return 4;} to just be an anonymous function, not a pointer to an anonymous function. That way you can write alias f = {return 4;}; which would just be an alias to a function, which makes sense. I haven't thought about how this would apply to delegates.


July 24, 2015
On 7/23/2015 6:05 PM, H. S. Teoh via Digitalmars-d wrote:
> It doesn't solve *all* the problems, but it does solve a
> significant subset of them.

The worst case of not having this feature is a compile time error. Not a runtime error, undetectable error, or silent corruption. A compile time error.

I also believe that you underestimate the nuisance significance of requiring the constraints cover 100% of everything the template body does.

Experience with something similar is with exception specifications. Even advocates of ES found themselves writing obviously crap code to work around the issue, because ES was so damned annoying.

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.
July 24, 2015
On Thursday, 23 July 2015 at 21:27:17 UTC, Ziad Hatahet wrote:
>> I think it is actually kinda pretty:
>
>
> What about:
>
> int median(int a, int b, int c) {
>     return (a<b) ? (b<c) ? b : (a<c) ? c : a : (a<c) ? a : (b<c) ? c : b;
> }
>
> vs.
>
> def median(a: Int, b: Int, c: Int) =
>   if (a < b) {
>     if (b < c) b
>     else if (a < c) c
>     else a
>   }
>   else if (a < c) a
>   else if (b < c) c
>   else b

Not really a spaces-to-spaces comparison...

to be honest, I'd probably just write that as:

int median(int a, int b, int c) {
   if (a < b) {
     if (b < c) return b;
     else if (a < c) return c;
     else return a;
   }
   else if (a < c) return a;
   else if (b < c) return c;
   else return b;
}

You don't need it to be an expression since it is a function, you can simply write return statements (which I kinda like since then it is obvious that that value is a terminating condition and not just the middle of some other calculation).

But if we were using a ternary, some newlines can help with it:

 return
   (a < b) ? (
     (b < c) ? b
     : (a < c) ? c
     : a
   )
   : (a < c) ? a
   : (b < c) ? c
   : b;

Indeed, there I just took your if/else thing and swapped out the else keyword for the : symbol, then replaced if(cond) with (cond) ?, then changed out the {} for (). It still works the same way.



> Is the compiler always able to always optimize out the function call by inlining it, as would be the case with
> a scope?

It should be.
July 24, 2015
On 7/23/2015 3:12 PM, Dicebot wrote:
> On Thursday, 23 July 2015 at 22:10:11 UTC, H. S. Teoh wrote:
>> OK, I jumped into the middle of this discussion so probably I'm speaking
>> totally out of context...
>
> This is exactly one major advantage of Rust traits I have been trying to
> explain, thanks for putting it up in much more understandable way :)

Consider the following:

    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();
    }

Now consider a deeply nested chain of function calls like this. At the bottom, one adds a call to 'color', and now every function in the chain has to add 'hasColor' even though it has nothing to do with the logic in that function. This is the pit that Exception Specifications fell into.

I can see these possibilities:

1. Require adding the constraint annotations all the way up the call tree. I believe that this will not only become highly annoying, it might make generic code impractical to write (consider if bar was passed as an alias).

2. Do the checking only for 1 level, i.e. don't consider what bar() requires. This winds up just pulling the teeth of the point of the constraint annotations.

3. Do inference of the constraints. I think that is indistinguishable from not having annotations as being exclusive.


Anyone know how Rust traits and C++ concepts deal with this?
July 24, 2015
Walter Bright <newshound2@digitalmars.com> wrote:
> On 7/23/2015 3:12 PM, Dicebot wrote:
>> On Thursday, 23 July 2015 at 22:10:11 UTC, H. S. Teoh wrote:
>>> OK, I jumped into the middle of this discussion so probably I'm speaking totally out of context...
>> 
>> This is exactly one major advantage of Rust traits I have been trying to explain, thanks for putting it up in much more understandable way :)
> 
> Consider the following:
> 
>     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();
>     }
> 
> Now consider a deeply nested chain of function calls like this. At the bottom, one adds a call to 'color', and now every function in the chain has to add 'hasColor' even though it has nothing to do with the logic in that function. This is the pit that Exception Specifications fell into.
> 
> I can see these possibilities:
> 
> 1. Require adding the constraint annotations all the way up the call
> tree. I believe that this will not only become highly annoying, it might
> make generic code impractical to write (consider if bar was passed as an alias).
> 
> 2. Do the checking only for 1 level, i.e. don't consider what bar()
> requires. This winds up just pulling the teeth of the point of the constraint annotations.
> 
> 3. Do inference of the constraints. I think that is indistinguishable from not having annotations as being exclusive.
> 
> 
> Anyone know how Rust traits and C++ concepts deal with this?

You may aus well ask "How do interfaces in OO programming deal with this?". Frankly, I've never had an issue with that. Or it's a hint for design problems.

Traits (and interfaces) are mostly not that fine grained, i.e. you don't
have a trait/interface for every method.
They should ideally define an abstraction/entity with a semantic meaning.
If your constraint "hasColor(x)" just means "x has method color()", and
then implement it for every class that has this method, you can just as
well omit constraints and use duck typing.

Tobi
July 24, 2015
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.

Tobi
July 24, 2015
On Fri, Jul 24, 2015 at 05:39:35AM +0000, Tobias Müller via Digitalmars-d wrote:
> Walter Bright <newshound2@digitalmars.com> wrote:
> > On 7/23/2015 3:12 PM, Dicebot wrote:
> >> On Thursday, 23 July 2015 at 22:10:11 UTC, H. S. Teoh wrote:
> >>> OK, I jumped into the middle of this discussion so probably I'm speaking totally out of context...
> >> 
> >> This is exactly one major advantage of Rust traits I have been trying to explain, thanks for putting it up in much more understandable way :)
> > 
> > Consider the following:
> > 
> >     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();
> >     }
> > 
> > Now consider a deeply nested chain of function calls like this. At the bottom, one adds a call to 'color', and now every function in the chain has to add 'hasColor' even though it has nothing to do with the logic in that function. This is the pit that Exception Specifications fell into.
> > 
> > I can see these possibilities:
> > 
> > 1. Require adding the constraint annotations all the way up the call tree. I believe that this will not only become highly annoying, it might make generic code impractical to write (consider if bar was passed as an alias).
> > 
> > 2. Do the checking only for 1 level, i.e. don't consider what bar() requires. This winds up just pulling the teeth of the point of the constraint annotations.
> > 
> > 3. Do inference of the constraints. I think that is indistinguishable from not having annotations as being exclusive.

Well, this is where the whole idea of Concepts comes from. Rather than specify the nitty-gritty of exactly which operations the type must support, you introduce an abstraction called a Concept, which basically is a group of related traits that a type must satisfy. You can think of it as a prototypical "type" that supports all the required operations.

For example, an input range would be a Concept that supports the operations .front, .empty, and .popFront. A forward range would be a larger Concept derived from the input range Concept, that adds the operation .save.

Your range functions don't have to specify explicitly that "type T must have methods called .empty, .front, .popFront", they simply say "type T must conform to the InputRange Concept".

	// Hypothetical syntax
	concept InputRange(ElementType) {
		bool empty;
		ElementType front();
		void popFront();
	}

	void myRangeFunc(R : InputRange)(R range)
	{
		// freely use .empty, .front, .popFront here
	}

This also solves your objection in the other post that specifying constraints will become too onerous because you have to keep listing every individual operation the function needs, and changing a function far down the call chain will bubble up and require updating all functions that call it. With Concepts, you don't have to do this, because any change can be done in the definition of the Concept itself.

If not, the function that requires more than what the current Concept provides actually needs a larger Concept that it's asking for, in which case all its callers *need* to be updated anyway. It's no different from deciding that a function that used to take struct S1 now needs to take struct S2 instead -- there's no way to avoid having to update all code that calls the function so that they pass in the new type.

Concepts can derive from other Concepts too, so a ForwardRange concept need not repeat the traits specified by the InputRange concept; it can simply specify that a forward range supports all InputRange traits, plus the .save method.

If you like, think of Concepts as compile-time interface definitions.


> > Anyone know how Rust traits and C++ concepts deal with this?
> 
> You may aus well ask "How do interfaces in OO programming deal with this?".  Frankly, I've never had an issue with that. Or it's a hint for design problems.
> 
> Traits (and interfaces) are mostly not that fine grained, i.e. you
> don't have a trait/interface for every method.
> They should ideally define an abstraction/entity with a semantic
> meaning.  If your constraint "hasColor(x)" just means "x has method
> color()", and then implement it for every class that has this method,
> you can just as well omit constraints and use duck typing.
[...]

Yes, the value of Concepts mostly comes from the, um, concepts that group together a set of traits that characterize a particular category of types. Like input range, forward range, or output range.  It's of more limited utility for testing traits individually.  You're not really thinking in terms of individual traits, at least not directly, when you're using Concepts; you're thinking in terms of the conceptual abstraction that the Concept represents. The compiler does the checking of individual traits for you.


T

-- 
The richest man is not he who has the most, but he who needs the least.
July 24, 2015
On Friday, 24 July 2015 at 04:42:59 UTC, Walter Bright wrote:
> On 7/23/2015 3:12 PM, Dicebot wrote:
>> On Thursday, 23 July 2015 at 22:10:11 UTC, H. S. Teoh wrote:
>>> OK, I jumped into the middle of this discussion so probably I'm speaking
>>> totally out of context...
>>
>> This is exactly one major advantage of Rust traits I have been trying to
>> explain, thanks for putting it up in much more understandable way :)
>
> Consider the following:
>
>     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();
>     }
>
> Now consider a deeply nested chain of function calls like this. At the bottom, one adds a call to 'color', and now every function in the chain has to add 'hasColor' even though it has nothing to do with the logic in that function. This is the pit that Exception Specifications fell into.
>
> I can see these possibilities:
>
> 1. Require adding the constraint annotations all the way up the call tree. I believe that this will not only become highly annoying, it might make generic code impractical to write (consider if bar was passed as an alias).
>
> 2. Do the checking only for 1 level, i.e. don't consider what bar() requires. This winds up just pulling the teeth of the point of the constraint annotations.
>
> 3. Do inference of the constraints. I think that is indistinguishable from not having annotations as being exclusive.
>
>

Fun begins:

	void foo(T)(T t)
	if(hasPrefix!T || hasSuffix!T){
		static if(...)t.prefix();
		else t.suffix();

		mixin("t.bar();");
	}

July 24, 2015
On 7/23/2015 11:05 PM, H. S. Teoh via Digitalmars-d wrote:
> Yes, the value of Concepts mostly comes from the, um, concepts that
> group together a set of traits that characterize a particular category
> of types. Like input range, forward range, or output range.  It's of more
> limited utility for testing traits individually.  You're not really
> thinking in terms of individual traits, at least not directly, when
> you're using Concepts; you're thinking in terms of the conceptual
> abstraction that the Concept represents. The compiler does the checking
> of individual traits for you.


That's true but it changes nothing about what I wrote. Just replace "hasPrefix" with "hasInterfaceA". The points I brought up remain.
July 24, 2015
On 7/23/2015 10:39 PM, Tobias Müller wrote:
> You may aus well ask "How do interfaces in OO programming deal with this?".

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.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18