July 25, 2015
On 7/25/2015 12:19 AM, Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= <ola.fosheim.grostad+dlang@gmail.com> wrote:
> The point of having a type system is to catch as many mistakes at compile time
> as possible. The primary purpose of a type system is to reduce flexibility.

Again, the D constraint system *is* a compile time system, and if the template body uses an interface not present in the type and not checked for in the constraint, you will *still* get a compile time error.

The idea that Rust traits check at compile time and D does not is a total misunderstanding.



BTW, you might want to remove the UTF-8 characters from your user name. Evidently, NNTP doesn't do well with them.
July 25, 2015
On 2015-07-24 21:04, Walter Bright wrote:

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

I'm not familiar with QueryInterface():

-- 
/Jacob Carlborg
July 25, 2015
On 2015-07-25 07:25, Walter Bright wrote:

> The one type that encompasses everything defeats the whole point of type
> checking, traits, concepts, etc.

Most methods only operate on a very specific set of data.

-- 
/Jacob Carlborg
July 25, 2015
On Saturday, 25 July 2015 at 08:58:26 UTC, Walter Bright wrote:
> It's still unusual to have 100% coverage in Phobos, and this is not because it is hard. Most of the time, it is easy to do. It's just that nobody checks it.

Yeah. I thought there had been a change to make it so that the coverage got printed out as part of the build, but I don't see it right now, at least on FreeBSD. Folks would still have to pay attention to those numbers though. Writing the tests is easy, and if someone is conscientious about their tests, I'd expect them to typically hit 100% without having to check (though you can still miss the occasional branch even then - especially with templated code), but frequently folks just write a few tests to make sure that the most basic functionality works and then call it a day.

I know that I'm too often guilty of assuming that I hit 100%, because I was thorough with my testing (since I tend to be _very_ thorough with my tests), and I should do better at verifying that I didn't miss something. I did put an effort a while back in making sure that std.datetime was as close to 100% as was possible, but I haven't checked it in a while...

Well, that's embarrassing. Almost all of the uncovered lines are lines that should never be run - e.g. assert(0) - but it does look like there are some lines which aren't covered which should be (not many in comparison to the whole, but there shouldn't be any). Interestingly though, the coverage is worse than it should be because it was generated from the release build on the unit tests, and stuff like invariants doesn't get run. I'll have to figure out how to get it to give me the coverage for the debug run of the tests, since that would be more accurate - though std.datetime can never actually hit 100% thanks to all of the assert(0) lines in it and the scope(failure) lines for printing out extra information when a test does fail. I suspect that I'm all of a percentage point off of what the max is.

Interestingly enough, @disable this() {} counts as a 0 too, even though the compiler should know that it's impossible for that to run even if the code is wrong - unlike assert(0). It _would_ be nice though if the assert(0) lines at least weren't counted, and it is kind of weird that the unit test lines count (though aside from scope(failure) lines, those should all run, though since I tend to put scope(failure) lines in unit tests for better output on failures, that's going to hurt my code coverage). So, _actually_ hitting 100% is likely to be annoyingly elusive for a lot of code, even if it's actually fully tested. But while std.datetime is almost as close as it can get, it's still _almost_ as close as it can get rather than all the way. :(

Clearly, I have a PR or two to write...

> Although we have succeeded in making unit tests part of the culture, the next step is 100% coverage.

Agreed.

> I know that 100% unit test coverage hardly guarantees code correctness. However, since I started using code coverage analyzers in the 1980s, the results are surprising - code with 100% test coverage has at LEAST an order of magnitude fewer bugs showing up in the field. It's surprisingly effective.

Oh, definitely. But while 100% unit test coverage is a huge step forward, I also think that for truly solid code, you want to go beyond that and make sure that you test corner cases and the like, test with a large enough variety of types with templates to catch behavioral bugs, etc. So, I don't think that we want to stop at 100% code coverage, but we do need to make sure that we're at 100% first and foremost.

> This is a huge reason why I want to switch to ddmd. I want to improve the quality of the compiler with unit tests.

That would definitely be cool.

> The various unit tests schemes I've tried for C++ are all ugly, inconvenient, and simply a bitch. It's like trying to use a slide rule after you've been given a calculator.

Yeah. It's not that hard to write one, and the tests themselves generally end up being pretty much the same as what you'd have in D, but there's still an annoying amount of boilerplate in getting them declared and set up - which is annoying enough in and of itself, but yeah, once you're used to D's unit tests, it seems particularly onerous.

> (I remember the calculator revolution. It happened my freshman year at college. September 1975 had $125 slide rules in the campus bookstore. December they were at $5 cutout prices, and were gone by January. I never saw anyone use a slide rule again. I've never seen a technological switchover happen so fast, before or since.)

If only folks thought that D's advantages over C++ were that obvious. ;)

- Jonathan M Davis
July 25, 2015
On 7/24/2015 7:28 PM, Jonathan M Davis wrote:
> I confess that I've always thought that QueryInterface was a _horrible_ idea,
> and that if you need to cast your type to something else like that, you're doing
> something wrong. *shudder* I really have nothing good to say about COM actually...

I am not explaining this properly. Trying again,

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

The Java, COM interface systems do not write code like:

    void foo(T: hasPrefix, hasSuffix)(T t) {
       t.prefix(); // ok
       bar(t);
    }


They write it something like:

    void foo(hasPrefix t) {
       t.prefix();
       s = cast(hasSuffix)t;
       if (s) bar(s);
       else RuntimeError(message);
    }

So no, statically checked traits and concepts are not used in the OOP world.
July 25, 2015
On 7/24/2015 10:59 PM, Jonathan M Davis wrote:
> In any case, looking at this, I have to agree with you that this is the same
> problem you get with checked exceptions / exceptions specifications - only worse
> really, because you can't do "throws Exception" and be done with it like you can
> in Java (as hideous as that is). Rather, you're forced to do the equivalent of
> listing all of the exception types being thrown and maintain that list as the
> code changes - i.e. you have to make the top-level template constraint list all
> of the sub-constraints and keep that list up-to-date as the sub-constraints
> change, which is a mess, especially with deep call stacks of templated functions.

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.
July 25, 2015
On Saturday, 25 July 2015 at 09:40:52 UTC, Walter Bright wrote:
> if the template body uses an interface not present in the type and not checked for in the constraint, you will *still* get a compile time error.

But only if the template gets instantiated with a bad type. Unit tests don't catch every thing and have to be written properly. A proper type system should catch it.
July 25, 2015
On Saturday, 25 July 2015 at 09:44:30 UTC, Jacob Carlborg wrote:
> On 2015-07-24 21:04, Walter Bright wrote:
>
>> 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.
>
> I'm not familiar with QueryInterface():

It's part of COM. Basically, it's equivalent to doing something like

auto f = cast(MyInterface)myObj;
if(f is null)
{ /+ you can't convert it to MyInterface +/ }
else
{ /+ you _can_ convert it +/ }

except that with the way that QueryInterface works, it's actually possible to get a completely different object out of it. It doesn't have to have any relation to the original object, and the new type doesn't have to be in an inheritance hierarchy with the original type. So, you can do wacky stuff like have an interface which relates to one object hierarchy and convert it to an interface from a completely unrelated object hierarchy just so long as the underlying type knows how to give you the type you're asking for (be it because it implements both interfaces or because it's designed to give you an object that implements the interface you're asking for).

So, QueryInterface isn't actually guaranteed to do anything like a cast at all. It just guarantees that you'll either get an object of the type you ask for - somehow - or that the call will fail.

And the way it's used often seems to be the equivalent of something like

interface A {}
interface A : B {}

interface Foo {}
interface Bar : Foo {}

class MyClass : B, Bar {}

Foo f = getFoo(); // returns what it is actually a MyClass
A a = cast(A)f;

It happens to work, because the underlying type implements both, but why on earth would you expect that a Foo would be related to an A when they're completely unrelated? And yet that seems to be the sort of thing that gets done with QueryInterface - at least in the code that I've worked with that does COM.

Personally, I think that it's nuts to even be attempting to cast from one interface to an entirely unrelated one and expect it to work - or to write code in a way that that's a normal way to do things.

- Jonathan M Davis
July 25, 2015
On Saturday, 25 July 2015 at 10:12:15 UTC, Jonathan M Davis wrote:
> On Saturday, 25 July 2015 at 09:44:30 UTC, Jacob Carlborg wrote:
>> On 2015-07-24 21:04, Walter Bright wrote:
>>
>>> 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.
>>
>> I'm not familiar with QueryInterface():
>
> It's part of COM. Basically, it's equivalent to doing something like
>
> auto f = cast(MyInterface)myObj;
> if(f is null)
> { /+ you can't convert it to MyInterface +/ }
> else
> { /+ you _can_ convert it +/ }
>
> except that with the way that QueryInterface works, it's actually possible to get a completely different object out of it. It doesn't have to have any relation to the original object, and the new type doesn't have to be in an inheritance hierarchy with the original type. So, you can do wacky stuff like have an interface which relates to one object hierarchy and convert it to an interface from a completely unrelated object hierarchy just so long as the underlying type knows how to give you the type you're asking for (be it because it implements both interfaces or because it's designed to give you an object that implements the interface you're asking for).
>
> So, QueryInterface isn't actually guaranteed to do anything like a cast at all. It just guarantees that you'll either get an object of the type you ask for - somehow - or that the call will fail.
>
> And the way it's used often seems to be the equivalent of something like
>
> interface A {}
> interface A : B {}
>
> interface Foo {}
> interface Bar : Foo {}
>
> class MyClass : B, Bar {}
>
> Foo f = getFoo(); // returns what it is actually a MyClass
> A a = cast(A)f;
>
> It happens to work, because the underlying type implements both, but why on earth would you expect that a Foo would be related to an A when they're completely unrelated? And yet that seems to be the sort of thing that gets done with QueryInterface - at least in the code that I've worked with that does COM.
>
> Personally, I think that it's nuts to even be attempting to cast from one interface to an entirely unrelated one and expect it to work - or to write code in a way that that's a normal way to do things.
>
> - Jonathan M Davis

It makes sense when one thinks about pure interfaces and component oriented programming, as preached by one of the Component Pascal guys. Or for that matter how Go uses interfaces.

The problem is when people try to model classical OOP on top of COM. This is one reason why on WinRT all classes implementing COM are required to be final.
July 25, 2015
On Saturday, 25 July 2015 at 10:01:43 UTC, Walter Bright wrote:
> On 7/24/2015 10:59 PM, Jonathan M Davis wrote:
>> In any case, looking at this, I have to agree with you that this is the same
>> problem you get with checked exceptions / exceptions specifications - only worse
>> really, because you can't do "throws Exception" and be done with it like you can
>> in Java (as hideous as that is). Rather, you're forced to do the equivalent of
>> listing all of the exception types being thrown and maintain that list as the
>> code changes - i.e. you have to make the top-level template constraint list all
>> of the sub-constraints and keep that list up-to-date as the sub-constraints
>> change, which is a mess, especially with deep call stacks of templated functions.
>
> 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.

Well, the caller then has to do deal with the fact that the result doesn't work with the next call in the chain, and at least in that case, the failures are at their level, not buried inside of calls that they're making. And this is exactly the sort of problem that we've had for quite a while where you try and do something like

auto result = rndGen().map!(a % 10).take(10).sort();

The result of take isn't random-access, but sort requires random-access, so you get a compilation failure - but it's clearly on this line and not inside of one of the calls that you're making, so it's pretty straightforward.

I guess that where the problem might come in (and maybe this is what you meant) is when you try and do a chain like that inside of a templated function, and you end up with a compilation failure, because the argument to that function resulted in one of the items in the chain failing. But I'm not sure that that's any different really from a line with a single function call failing due to the outer function's argument not working with it.

> It also may completely torpedo Andrei's Design By Introspection technique.

I'm not sure what you mean here. Design by Introspection seems to be primarily for implementing optional functionality, and it couldn't be in the outer template constraint regardless, because it's optional. So, I don't really see how whether or not we put all of the sub-constraints in the main template constraint really affects DbI.

I do have to wonder about what Andrei means to do with DbI though, since he keeps bringing it up like it solves everything and should be used everywhere, whereas it seems like it would only to apply to certain areas where you're dealing with optional functionality, and a lot of code wouldn't benefit from it all. We're essentially using it with ranges already when we're implementing algorithms differently based on what type of range we're given or what extra capabilities the range has, so it obviously is showing its usefulness there, but the allocators is the only other case that I can think of at the moment where it would make sense to use it heavily. He sounds like he wants to use it everywhere, which I don't get at all.

- Jonathan M Davis