July 25, 2015
On Sat, 2015-07-25 at 02:40 -0700, Walter Bright via Digitalmars-d wrote:
> 
[[…]
> BTW, you might want to remove the UTF-8 characters from your user name. Evidently, NNTP doesn't do well with them.

Conversely someone should fix the NNTP implementation to deal properly
with UTF-8 encoded Unicode codepoints.

-- 
Russel. ============================================================================= Dr Russel Winder      t: +44 20 7585 2200   voip: sip:russel.winder@ekiga.net 41 Buckmaster Road    m: +44 7770 465 077   xmpp: russel@winder.org.uk London SW11 1EN, UK   w: www.russel.org.uk  skype: russel_winder


July 25, 2015
On Saturday, 25 July 2015 at 10:01:43 UTC, Walter Bright wrote:
> Phew, finally, someone understands what I'm talking about! I'm really bad at explaining things to people that they aren't already familiar with.
>
> I'm not sure, but I suspect this problem may cripple writing generic library functions that do one operation and then forward to the next (unknown in advance) operation in a chain.
>
> It also may completely torpedo Andrei's Design By Introspection technique.

Actually I don't think the problem you state is actually a problem at all, even disregarding my previous argument(which is still think is valid). The key point is that it would be opt-in and it would trickle down, not up. Normal templates without the concept/traits/interface things would still be able to call functions with the extra constraints with out needing to add it to them selves.

For instance, say the syntax to specialize on one of these concept/traits/interface things was the same as specializing on a class, eg:

void foo(T : inputRange)(T x){}

Calling foo from any where would still be the same, even calling it from other templates with out the concept/traits/interface things. eg the following would work:

void bar(T)(T x){ foo(x); }

Because bar is a normal template, it still has no choice but to assume that T can do any thing we ask it to do, because that is what we have always done with templates. So the template happily assumes that passing x into foo will work. If for some reason you pass a type in that is not an inputRange, then it will fail at instantiation. So far it is the same as the constraints we have now.

Ok, here is where it is different.

In side of foo, it would be illegal to do anything other than inputRange stuff with x. For instance the following would be illegal:

void foo(T : inputRange)(T x)
{
     x.something(); // ERROR!
}

The real kicker here, is that THAT error can be detected without ever instantiating foo. No need to rely on unittests, which may or may not catch it depending on which types we use.

Ok now take it a step further. Say we have the following:

void foo(T : inputRange)(T x)
{
     bar(x); // ERROR!
}

void bar(T : someOtherInterface)(T x){}

The previous would error! Why? Because foo only assumes x can do inputRange things, and when you pass it into bar it asks it to do someOtherInterface which it doesn't know it can do! This all would still error with out every instantiating the template!

Also the following should also error:

void foo(T : inputRange)(T x)
{
     bar(x); // ERROR!
}

void bar(T)(T x) if(isSomeOtherInterface!T) {}

Why? Because from inside foo, it is only assumed that x can do inputRange things, when it gets passed into bar the constraint will ask it to do non inputRange things and fail! Still with out foo being instantiated! Even something like the following should error:

void foo(T : inputRange)(T x)
{
     bar(x); // ERROR!
}

void bar(T)(T x) { x.something_inputranges_dont_have(); }

This would error for the same reasons as before, bar asked x to do non input range things. In contrast the following would be ok!

void foo(T : inputRange)(T x)
{
     bar(x); // Ok
}

void bar(T)(T x) { foreach(i;x){} }

That still works because it is known that x can do inputRange things, so its ok! Woo!

This is awesome right? All these errors being caught without ever instantiating the templates. You should still instantiate them and test them, but the value is that the errors were caught sooner, with out even instantiating them.

The main difference here is that a normal template assumes that a type can do anything until it actually gets instantiated. Add some constraints(the normal ones we have now) and you can filter for things that don't do X, but you still assume that the type can do any thing else in addition to X. On the other hand, the concept/traits/interface things would be as conservative as possible and only assume a type can do what its concept/traits/interface things say it can do.


In summery, the concept/traits/interface things would not require them to applied to the whole tree. Doing so would break how templates work now and really just does not make sense unless things were being redone from scratch. They are opt-in! In addition to that, they catch a bunch of bugs in templates before they are ever instantiated! This is a good thing.

July 25, 2015
On 7/23/15 3:50 PM, H. S. Teoh via Digitalmars-d wrote:
> On Thu, Jul 23, 2015 at 12:49:29PM -0700, Walter Bright via Digitalmars-d wrote:
>> On 7/23/2015 7:15 AM, Andrei Alexandrescu wrote:
>>>> I am a bit puzzled by the notion of shipping template code that has
>>>> never been instantiated as being a positive thing. This has also
>>>> turned up in the C++ static_if discussions.
>>>
>>> This is easy to understand. Weeding out uncovered code during
>>> compilation is a central feature of C++ concepts. Admitting you
>>> actually never want to do that would be a major blow.
>>
>> But if a unit test fails at instantiating it, it fails at compile
>> time.
>
> That assumes the template author is diligent (foolhardy?) enough to
> write unittests that cover all possible instantiations...

Well at least all paths must be compiled. You wouldn't ship templates that were never instantiated just as much as you wouldn't ship any code without compiling it. We've had a few cases in Phobos a while ago of templates that were never instantiated, with simple compilation errors when people tried to use them. -- Andrei

July 25, 2015
On 7/23/15 4:08 PM, Dicebot wrote:
> On Thursday, 23 July 2015 at 19:55:30 UTC, Walter Bright wrote:
>> On 7/23/2015 8:03 AM, Dicebot wrote:
>>> At the same time one HUGE deal breaker with rust traits that rarely gets
>>> mentioned is the fact that they are both constraints and interfaces
>>> at the same
>>> time:
>>>
>>> // this is template constraint, it will generate new `foo` symbol for
>>> each new T
>>> fn foo <T : InputRange> (range : T)
>>>
>>> // this use the very same trait definition but creates "fat pointer"
>>> on demand
>>> with simplistic dispatch table
>>> fn foo (range : InputRange)
>>>
>>> It kills all the necessity for hacks like RangeObject and is quite a
>>> salvation
>>> once you get to defining dynamic shared libraries with stable ABI.
>>>
>>> This is probably my most loved feature of Rust.
>>
>> D interface types also produce the simplistic dispatch table, and if
>> you make them extern(C++) they don't need a RangeObject. I know it
>> isn't as convenient as what you describe above, but it can be pressed
>> into service.
>
> I am not sure how it applies. My point was about the fact that
> `isInputRange` and `InputRangeObject` are the same entities in Rust,
> simply interpreted differently by compiler depending on usage context.
>
> This is important because you normally want to design your application
> in terms of template constraints and structs to get most out of inlining
> and optimization. However, to define stable ABI for shared libraries,
> the very same interfaces need to be wrapped in runtime polymorphism.
>
> Closest thing in D would be to define traits as interfaces and use code
> like this:
>
> void foo(T)()
>      if (  (is(T == struct) || is(T == class))
>         && Matches!(T, Interface)
>      )
> { }
>
> where `Matches` is a template helper that statically iterates method
> list of interface and looks for matching methods in T.

I could have sworn we have implementsInterface in std.traits.

> However, making
> it built-in feels really convenient in Rust:
>
> - considerably less function declaration visual noise
> - much better error messages: trying to use methods of T not defined by
> a trait will result in compile-time error even without instantiating the
> template

Yah, building stuff in does have its advantages.


Andrei

July 25, 2015
On 7/23/15 4:52 PM, Walter Bright wrote:
> On 7/23/2015 1:08 PM, Dicebot wrote:
>  > I am not sure how it applies.
>
> D interfaces (defined with the 'interface' keyword) are simple dispatch
> types, they don't require an Object. Such interfaces can also have
> default implementations.

Is this new? I agree we should allow it, but I don't think it was added to the language yet.

Andrei
July 25, 2015
On 7/23/15 4:47 PM, Ziad Hatahet via Digitalmars-d wrote:
> On the other hand, Rust does not require parenthesis around if
> conditions:

Yet it requires braces around the arms. Rust taketh away, Rust requireth back :o). -- Andrei
July 25, 2015
On 7/23/15 5:26 PM, Ziad Hatahet via Digitalmars-d wrote:
> On Thu, Jul 23, 2015 at 2:00 PM, Adam D. Ruppe via Digitalmars-d
> <digitalmars-d@puremagic.com <mailto:digitalmars-d@puremagic.com>> 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

This is a wash. If we want to discuss good things in Rust we could get inspiration from, we need relevant examples. -- Andrei

July 25, 2015
On 7/23/15 6:06 PM, H. S. Teoh via Digitalmars-d wrote:
> An uninstantiated template path is worse than a branch that's never
> taken, because the compiler can't help you find obvious problems before
> you ship it to the customer.

-cov does help, although indeed in a suboptimal way because you need to manually parse the listing. It would be nice if it marked lines that are supposed to be code yet are not instantiated in a specific way. (Currently it only marks lines that are compiled but not run.)

> A lot of Phobos bugs lurk in rarely-used template branches that are not
> covered by the unittests.

Hopefully not many are left. I consider that a historical problem caused by lack of good testing discipline. My perception is we got a lot better at that.

It's simple survival to not ship untested code, even if it does compile. In any language. We should never do it.

> If we had a Concepts-like construct in D, where template code is
> statically constrained to only use, e.g., range API when manipulating an
> incoming type, a lot of these bugs would've been caught.

We'd get to ship more untested code? No thanks. We need a different angle on this. Concepts support a scenario that fails basic software engineering quality assurance criteria.

> In fact, I'd argue that this should be done for *all* templates -- for
> example, a function like this ought to be statically rejected:
>
> 	auto myFunc(T)(T t) { return t + 1; }
>
> because it assumes the validity of the + operation on T, but T is not
> constrained in any way, so it can be *any* type, most of which,
> arguably, do not support the + operation.

That would be a bit much. myFunc is correct under static and dynamic assumptions about T. Dynamic assumptions cannot be checked save for documentation and unittesting. If the static assumptions fail, then well we have a less-than-nice compile-time error message, but still a compile-time error message. No disaster.

> Someone could easily introduce a bug:
>
> 	auto myFunc(T)(T t)
> 		if (is(typeof(T.init + 1)))
> 	{
> 		/* Oops, we checked that +1 is a valid operation on T,
> 		 * but here we're doing -1 instead, which may or may not
> 		 * be valid: */
> 		return t - 1;
>

Is that a bug or a suboptimal error message?

> The compiler still accepts this code as long as the unittests use types
> that support both + and -. So this dependency on the incidental
> characteristics of T remains as a latent bug.
>
> If the compiler outright rejected any operation on T that hasn't been
> explicitly tested for, *then* we will have eliminated a whole class of
> template bugs. Wrong code like the last example above would be caught as
> soon as the compiler compiles the body of myFunc.

Yah, I think this is off.


Andrei


July 25, 2015
On 7/23/15 9:05 PM, H. S. Teoh via Digitalmars-d wrote:
> Yes, but by then it's the user that is faced with an inscrutable
> template error. If I'm a library author, I'd like to be able to find all
> these bugs*before*  shipping my code to the customers.

Then you need to understand that concepts are not helping you with that. -- Andrei
July 25, 2015
On 7/24/15 2:05 AM, H. S. Teoh via Digitalmars-d wrote:
> 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".

As I argued in "Generic Programming Must Go", this does work on scarce-vocabulary domain. The moment you start talking about ranges that may or may not support cross-cutting primitives, it all comes unglued. -- Andrei