July 23, 2015
On Thu, Jul 23, 2015 at 2:00 PM, Adam D. Ruppe via Digitalmars-d < 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


Before you get too worried about the (), I'd point out that this is a very
> common pattern in Javascript (for like everything...) and while everybody hates JS, most every uses it too; this patten is good enough for usefulness.
>

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


July 23, 2015
On Thu, Jul 23, 2015 at 08:50:55PM +0000, Vlad Levenfeld via Digitalmars-d wrote:
> On Thursday, 23 July 2015 at 20:40:17 UTC, Walter Bright wrote:
> >On 7/23/2015 12:50 PM, H. S. Teoh via Digitalmars-d wrote:
> >>That assumes the template author is diligent (foolhardy?) enough to write unittests that cover all possible instantiations...
> >
> >No, only each branch of the template code must be instantiated, not every possible instantiation. And we have a tool to help with that: -cov
> >
> >Does anyone believe it is a good practice to ship template code that has never been instantiated?
> 
> I dunno about good practices but I have some use cases.
> 
> I write a bunch of zero-parameter template methods and then pass them into a Match template which attempts to instantiate each of them in turn, settling on the first one which does compile. So the methods basically form a list of "preferred implementation of functionality X". All but one winds up uninstantiated.
[...]

But don't you still have to test each template, to make sure they compile when they're supposed to?


T

-- 
Without geometry, life would be pointless. -- VS
July 23, 2015
On Thu, 23 Jul 2015 13:46:16 -0700, Walter Bright wrote:
> like implicit declaration of variables.

Trigger warning needed!
July 23, 2015
On Thu, Jul 23, 2015 at 01:40:17PM -0700, Walter Bright via Digitalmars-d wrote:
> On 7/23/2015 12:50 PM, H. S. Teoh via Digitalmars-d wrote:
> >That assumes the template author is diligent (foolhardy?) enough to write unittests that cover all possible instantiations...
> 
> No, only each branch of the template code must be instantiated, not every possible instantiation. And we have a tool to help with that: -cov
> 
> Does anyone believe it is a good practice to ship template code that has never been instantiated?

OK, I jumped into the middle of this discussion so probably I'm speaking totally out of context... but anyway, with regards to template code, I agree that it ought to be thoroughly tested by at least instantiating the most typical use cases (as well as some not-so-typical use cases).

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.

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

Instantiating all branches is only part of the solution, though. A lot of Phobos bugs also arise from undetected dependencies of the template code on the specifics of the concrete types used to test it in the unittests.  The template passes the unittest but when you instantiate it with a type not used in the unittests, it breaks. For instance, a lot of range-based templates are tested with arrays in the unittests. Some of these templates wrongly depend on array behaviour (as opposed to being confined only to range API operations) while their signature constraints indicate only the generic range API. As a result, when non-array ranges are used, it breaks. Sometimes bugs like this can lurk undetected for a long time before somebody one day happens to instantiate it with a range type that violates the hidden assumption in the template code.

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.

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.

Instead, templates ought to be required to explicitly declare up-front all operations that it will perform on incoming types, so that (1) its assumptions are obvious, and (2) the compiler will reject attempts to instantiate it with an incompatible type.

	auto myFunc(T)(T t)
		if (is(typeof(T.init + 1)))
	{
		return t + 1;
	}

The current syntax is ugly, of course, but that's easily remedied. The more fundamental problem is that the compiler does not restrict operations on T in any way, even when the sig constraint specifies how T ought to be used. 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;
	}

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.


T

-- 
Elegant or ugly code as well as fine or rude sentences have something in common: they don't depend on the language. -- Luca De Vitis
July 23, 2015
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 :)
July 23, 2015
On Thursday, 23 July 2015 at 14:08:23 UTC, Andrei Alexandrescu wrote:
> Thanks for the link, good quick read to get the overview of Rust's traits feature. It's ingenious because it integrates static and dynamic dispatch.
>
> For dynamic dispatch, traits are better than interfaces - more flexible, better informed. For static dispatch, they don't hold a candle to D's constraints. This is important because dynamic dispatch is more of a cut-and-dried matter, whereas static dispatch is where it's at.
>

On that note, I've mentioned scala's trait, which are kind of similar and worth looking at. The thing being based on java's object model, as D's object model, it is easier to think about how this could get into D.

> For static dispatch I think D's template constraints are quite a lot better; they have a lot more power and offer a lot more to promise. They are an out-of-the-box solution that's a bit unwieldy because it's new enough to not yet have established idioms. In contrast, traits come from straight within the box.
>

Certainly, but they suffer from the LISP effect. You can do everything because the structure does not constrain you in any way, while at the same time it become quickly very hard to understand, for the very same reason.

I do think think the opposition between the 2, as seen in your post, or Stroustrup's allergy to static if is wrong headed.

May be one can be expressed via the other ?

July 24, 2015
On 7/23/2015 2:08 PM, Dicebot wrote:
> It does not protect from errors in definition
>
> void foo (R) (Range r)
>      if (isInputRange!Range)
> { r.save(); }
>
> unittest
> {
>      SomeForwardRange r;
>      foo(r);
> }
>
> This will compile and show 100% test coverage. Yet when user will try using it
> with real input range, it will fail.

That is correct. Some care must be taken that the mock types used in the unit tests actually match what the constraint is, rather than being a superset of them.


> There is quite a notable difference in clarity between error message coming from
> some arcane part of function body and referring to wrong usage (or even totally
> misleading because of UFCS) and simple and straightforward "Your type X does not
> implement method X necessary for trait Y"

I believe they are the same. "method X does not exist for type Y".


> Coverage does not work with conditional compilation:
>
> void foo (T) ()
> {
>      import std.stdio;
>      static if (is(T == int))
>          writeln("1");
>      else
>          writeln("2");
> }
>
> unittest
> {
>      foo!int();
> }
>
> $ dmd -cov=100 -unittest -main ./sample.d

Let's look at the actual coverage report:
===============================
       |void foo (T) ()
       |{
       |    import std.stdio;
       |    static if (is(T == int))
      1|        writeln("1");
       |    else
       |        writeln("2");
       |}
       |
       |unittest
       |{
      1|    foo!int();
       |}
       |
foo.d is 100% covered
============================

I look at these all the time. It's pretty obvious that the second writeln is not being compiled in.

Now, if I make a mistake in the second writeln such that it is syntactically correct yet semantically wrong, and I ship it, and it blows up when the customer actually instantiates that line of code,

   -- where is the advantage to me? --

How am I, the developer, better off? How does "well, it looks syntactically like D code, so ship it!" pass any sort of professional quality assurance?

July 24, 2015
On 7/23/2015 3:06 PM, H. S. Teoh via Digitalmars-d wrote:
> OK, I jumped into the middle of this discussion so probably I'm speaking
> totally out of context... but anyway, with regards to template code, I
> agree that it ought to be thoroughly tested by at least instantiating
> the most typical use cases (as well as some not-so-typical use cases).

I argue that every code line of the template must at least have been instantiated at some point by the test suite. Anything less is, frankly, unprofessional.


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

Generally when I work on a Phobos template, I upgrade it to 100% unit test coverage. This should be a minimum bar for all Phobos work. We ought to be ashamed of anything less.


> Instantiating all branches is only part of the solution, though. A lot
> of Phobos bugs also arise from undetected dependencies of the template
> code on the specifics of the concrete types used to test it in the
> unittests.  The template passes the unittest but when you instantiate it
> with a type not used in the unittests, it breaks. For instance, a lot of
> range-based templates are tested with arrays in the unittests. Some of
> these templates wrongly depend on array behaviour (as opposed to being
> confined only to range API operations) while their signature constraints
> indicate only the generic range API. As a result, when non-array ranges
> are used, it breaks. Sometimes bugs like this can lurk undetected for a
> long time before somebody one day happens to instantiate it with a range
> type that violates the hidden assumption in the template code.

I agree that the constraint system is not checked against the actual body of the template. Dicebot brought that up as well. Some attention should be paid in the unit tests to using types that are minimal implementations of the constraints.

That said, it is a pipe dream to believe that if something matches the function signatures, that it is correct and will work without ever having been tested.


> 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.
>
> 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.

It's a valid point, but I'd counter that it'd be pretty tedious and burdensome. D isn't meant to be a bondage & discipline language. The failed exception specifications (Java and C++) comes to mind.


> 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.

Yeah, but few would like programming in such a nagging, annoying language. Note that if you do instantiate with a type that doesn't support those operations, it isn't the end of the world - you'll still get a compile time error message.

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

July 24, 2015
On Thu, Jul 23, 2015 at 05:34:20PM -0700, Walter Bright via Digitalmars-d wrote:
> On 7/23/2015 3:06 PM, H. S. Teoh via Digitalmars-d wrote:
> >OK, I jumped into the middle of this discussion so probably I'm speaking totally out of context... but anyway, with regards to template code, I agree that it ought to be thoroughly tested by at least instantiating the most typical use cases (as well as some not-so-typical use cases).
> 
> I argue that every code line of the template must at least have been instantiated at some point by the test suite. Anything less is, frankly, unprofessional.

I agree, and I didn't claim otherwise.


> >A lot of Phobos bugs lurk in rarely-used template branches that are not covered by the unittests.
> 
> Generally when I work on a Phobos template, I upgrade it to 100% unit test coverage. This should be a minimum bar for all Phobos work. We ought to be ashamed of anything less.

Agreed.


> >Instantiating all branches is only part of the solution, though. A lot of Phobos bugs also arise from undetected dependencies of the template code on the specifics of the concrete types used to test it in the unittests.  The template passes the unittest but when you instantiate it with a type not used in the unittests, it breaks. For instance, a lot of range-based templates are tested with arrays in the unittests. Some of these templates wrongly depend on array behaviour (as opposed to being confined only to range API operations) while their signature constraints indicate only the generic range API. As a result, when non-array ranges are used, it breaks. Sometimes bugs like this can lurk undetected for a long time before somebody one day happens to instantiate it with a range type that violates the hidden assumption in the template code.
> 
> I agree that the constraint system is not checked against the actual body of the template. Dicebot brought that up as well. Some attention should be paid in the unit tests to using types that are minimal implementations of the constraints.
> 
> That said, it is a pipe dream to believe that if something matches the function signatures, that it is correct and will work without ever having been tested.

I didn't say that this one thing alone will singlehandedly solve all of our template testing woes. Obviously, it cannot catch semantic errors -- you use all the valid range API operations, but you use them in the wrong order, say, or in a way that doesn't accomplish what the code is supposed to do.  I think it's a given that you still need to adequately unittest the code just like you would non-template code.

Nevertheless, this does help to eliminate an entire class of latent template bugs -- hidden dependencies on the incoming type that are not covered by the function's contract (i.e., signature constraints). Relying on the programmer to always use types with minimal functionality in the unittests is programming by convention, and you know very well how effective that is. Without enforcement, we have no way of being sure that our tests are actually adequate. An untested branch of template code can be detected by using -cov, but performing an operation on an incoming type without checking for it in the sig constraints cannot be detected except by reading every line of code. The unittest may have inadvertently used a type with a superset of functionality, but since this is never enforced (and the current language provides no way to actually enforce it) we can never be sure -- we're just taking it on faith that the tests have covered all bases.

With actual language enforcement, we can actually provide some guarantees. It doesn't solve *all* the problems, but it does solve a significant subset of them.


> >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.
> >
> >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.
> 
> It's a valid point, but I'd counter that it'd be pretty tedious and burdensome. D isn't meant to be a bondage & discipline language. The failed exception specifications (Java and C++) comes to mind.
> 
> 
> >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.
> 
> Yeah, but few would like programming in such a nagging, annoying language.

I have trouble thinking of a template function that's actually *correct* when its sig constraints doesn't specify what operations are valid on the incoming type. Can you give an example?

If such code is wrong, I'd say the language *should* reject it.

If you think that's too "bondange and discipline", what about a generic wildcard sig constraint clause that says basically "type T works with any operation you imagine"? Then those programmers who are too lazy to figure out what operations are required for the function can just slap this on, and continue writing broken code to their heart's content.


> Note that if you do instantiate with a type that doesn't support those operations, it isn't the end of the world - you'll still get a compile time error message.

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.


T

-- 
People tell me I'm stubborn, but I refuse to accept it!