Jump to page: 1 2 3
Thread overview
Interfaces, traits, concepts, and my idea for a DIP
Jul 28, 2015
Atila Neves
Jul 28, 2015
Jack Stouffer
Jul 28, 2015
Atila Neves
Jul 29, 2015
Kagamin
Jul 29, 2015
Atila Neves
Jul 28, 2015
Daniel Kozák
Jul 28, 2015
jmh530
Jul 28, 2015
Tofu Ninja
Jul 31, 2015
Sean Campbell
Jul 29, 2015
ChangLong
Jul 29, 2015
Roland Hadinger
Jul 29, 2015
Atila Neves
Jul 29, 2015
Tofu Ninja
Jul 29, 2015
Tofu Ninja
Jul 30, 2015
Atila Neves
Jul 30, 2015
jmh530
Jul 31, 2015
Atila Neves
Jul 31, 2015
Biotronic
Jul 31, 2015
cym13
Jul 31, 2015
Atila Neves
Jul 31, 2015
Atila Neves
Jul 31, 2015
jmh530
Aug 01, 2015
Piotrek
Jul 31, 2015
Sebastiaan Koppe
Jul 31, 2015
cym13
Jul 29, 2015
Atila Neves
July 28, 2015
So I missed the boat on the lengthy Rust traits discussion on the other thread. I confess I didn't have time to read all of it so forgive me if I bring up something that's already been said there.

Since last year, after having written a ton of generic code with compile-time reflection and (although it didn't have a name at the time) design by introspection, I got pretty annoyed at having to debug why my template constraints failed. So I had an idea and it's been waiting for me to write a DIP ever since. I don't think I have time right now to write the DIP properly, but given the recent discussion, here's a sketch. Say I have:

void func(R)(R range) if(isInputRange!R) { ... }

Maybe I didn't even write the function, maybe it's in Phobos. But I'm interested in implementing an input range but this fails to instantiate. I have no idea why. Now, it's usually good practice to include a `static assert(isInputRange!MyType)` somewhere, but if that fails... you have no idea why. Was it `popFront`? `front`? `empty`? Why didn't it compile?

I've resorted to copying the code from the lambda in the customary `is(typeof({...}))` in the template constraint and forcing the compiler to give the message that it's hiding from me. The compiler knows why the static assert failed, but it won't tell me unless I force it to.

This is analogous to dynamic dispatch except, in that case, the compiler will quite gladly tell you if you forgot to implement a virtual function and much more besides. And why can it do that? Because you told it your class implements an interface or inherits from a class with abstract functions / methods.

So... instead of having traits / concepts, what I wanted from D is to be able to do this:

struct MyRange: isInputRange { ... }

or

struct MyRange: static isInputRange { ... } // that way classes could do this too

Then instead of:

foo.d(9): Error: static assert  (isInputRange!(MyRange)) is false

I'd get:

foo.d(9): Error: MyRange does not implement popFront


I'd hoped to paste the result of trying to compile isInputRange in the 2nd example but apparently right now I get the very horrible:

foo.d(17): Error: template std.range.primitives.popFront cannot deduce function from argument types !()(MyRange), candidates are:
/usr/include/dlang/dmd/std/range/primitives.d(1992):        std.range.primitives.popFront(T)(ref T[] a) if (!isNarrowString!(T[]) && !is(T[] == void[]))
/usr/include/dlang/dmd/std/range/primitives.d(2015):        std.range.primitives.popFront(C)(ref C[] str) if (isNarrowString!(C[]))
foo.d(22): Error: template instance foo.func!(MyRange) error instantiating



At least it mentions `popFront`. Anyway, you understand what I want from this. The only I think I've seen that comes close to achieving this is a massive hack abusing __ctfe in Adam's book.

I've lost count of how many times a template constraint failed because of some stupid thing like @safe functions not being able to call @system ones and it was really hard figuring out why.

I don't want C++ concepts. I just want compiler error messages pointing me in the general direction of why my type fails a template constraint. This is especially bad when there are several overloads with different constraints, I usually have to comment out code until the offending one presents itself.

Atila
July 28, 2015
Overall a great idea!

On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:
> struct MyRange: static isInputRange { ... }

I don't think you even need new syntax to get the error messages. If you have a static assert in your unit tests for isInputRange!MyType, then theoretically you could just modify isInputRange to return error messages, right?
July 28, 2015
On Tuesday, 28 July 2015 at 13:02:06 UTC, Jack Stouffer wrote:
> Overall a great idea!
>
> On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:
>> struct MyRange: static isInputRange { ... }
>
> I don't think you even need new syntax to get the error messages. If you have a static assert in your unit tests for isInputRange!MyType, then theoretically you could just modify isInputRange to return error messages, right?

I guess, but not easily. I've written template mixins to do that before and it was awkward.

Atila
July 28, 2015
On Tue, 28 Jul 2015 12:49:15 +0000
"Atila Neves" <atila.neves@gmail.com> wrote:

> So I missed the boat on the lengthy Rust traits discussion on the other thread. I confess I didn't have time to read all of it so forgive me if I bring up something that's already been said there.
> 
> Since last year, after having written a ton of generic code with compile-time reflection and (although it didn't have a name at the time) design by introspection, I got pretty annoyed at having to debug why my template constraints failed. So I had an idea and it's been waiting for me to write a DIP ever since. I don't think I have time right now to write the DIP properly, but given the recent discussion, here's a sketch. Say I have:
> 
> void func(R)(R range) if(isInputRange!R) { ... }
> 
> Maybe I didn't even write the function, maybe it's in Phobos. But I'm interested in implementing an input range but this fails to instantiate. I have no idea why. Now, it's usually good practice to include a `static assert(isInputRange!MyType)` somewhere, but if that fails... you have no idea why. Was it `popFront`? `front`? `empty`? Why didn't it compile?
> 
> I've resorted to copying the code from the lambda in the customary `is(typeof({...}))` in the template constraint and forcing the compiler to give the message that it's hiding from me. The compiler knows why the static assert failed, but it won't tell me unless I force it to.
> 
> This is analogous to dynamic dispatch except, in that case, the compiler will quite gladly tell you if you forgot to implement a virtual function and much more besides. And why can it do that? Because you told it your class implements an interface or inherits from a class with abstract functions / methods.
> 
> So... instead of having traits / concepts, what I wanted from D is to be able to do this:
> 
> struct MyRange: isInputRange { ... }
> 
> or
> 
> struct MyRange: static isInputRange { ... } // that way classes could do this too
> 
> Then instead of:
> 
> foo.d(9): Error: static assert  (isInputRange!(MyRange)) is false
> 
> I'd get:
> 
> foo.d(9): Error: MyRange does not implement popFront
> 
> 
> I'd hoped to paste the result of trying to compile isInputRange in the 2nd example but apparently right now I get the very horrible:
> 
> foo.d(17): Error: template std.range.primitives.popFront cannot
> deduce function from argument types !()(MyRange), candidates are:
> /usr/include/dlang/dmd/std/range/primitives.d(1992):
> std.range.primitives.popFront(T)(ref T[] a) if
> (!isNarrowString!(T[]) && !is(T[] == void[]))
> /usr/include/dlang/dmd/std/range/primitives.d(2015):
> std.range.primitives.popFront(C)(ref C[] str) if
> (isNarrowString!(C[]))
> foo.d(22): Error: template instance foo.func!(MyRange) error
> instantiating
> 
> 
> 
> At least it mentions `popFront`. Anyway, you understand what I want from this. The only I think I've seen that comes close to achieving this is a massive hack abusing __ctfe in Adam's book.
> 
> I've lost count of how many times a template constraint failed because of some stupid thing like @safe functions not being able to call @system ones and it was really hard figuring out why.
> 
> I don't want C++ concepts. I just want compiler error messages pointing me in the general direction of why my type fails a template constraint. This is especially bad when there are several overloads with different constraints, I usually have to comment out code until the offending one presents itself.
> 
> Atila


I was thinking about same many times before. With one difference:

instead of this:
struct MyRange: isInputRange { ... }

I would use something like this:

@interface(InputRange, ...)
struct MyRange { ... }


@interface(InputRange, ...)
class MyClassRange { ... }



July 28, 2015
On Tuesday, 28 July 2015 at 13:23:37 UTC, Daniel Kozák wrote:
>
> I would use something like this:
>
> @interface(InputRange, ...)
> struct MyRange { ... }
>
>
> @interface(InputRange, ...)
> class MyClassRange { ... }

I get the change from isInputRange to InputRange, because isInputRange is like a templated bool or something and InputRange is an interface. But why the suggestion of user-defined attributes?

As an aside, I'm still trying to understand the reasoning behind some of the decisions around structs and inheritance. The big difference between structs and classes is that structs are value types and classes are reference types. I'm not entirely sure on all the implications of that. My guess is that it helps to know how big a value type is in advance (at compilation). So if you're inheriting virtual methods in a struct, then you won't necessarily know how big it will be and that can be problem. However, I would think that final and static methods wouldn't be an issue.

Coming around to interfaces, they only allow virtual methods without implementation, plus final and static methods. I would think the last two would work fine with structs because they should be known at compile time, so the only part I'm not sure about is the way the virtual methods without implementation would work. It seems like to implement what the OP is suggesting you'd need some separate rule for handling struct inheritance of interfaces so that they are not virtual in the same manner as they would be for classes.
July 28, 2015
On Tuesday, 28 July 2015 at 13:23:37 UTC, Daniel Kozák wrote:
>
> I was thinking about same many times before. With one difference:
>
> instead of this:
> struct MyRange: isInputRange { ... }
>
> I would use something like this:
>
> @interface(InputRange, ...)
> struct MyRange { ... }
>
>
> @interface(InputRange, ...)
> class MyClassRange { ... }

I have actually thought about this as well, and a thing that could actually make this possible is if UDAs could get the thing they are attached to. I realized it could be done easily when I saw that this worked:

template someuda(alias attach) {}
@someuda!(x) int x;

someuda can even inspect x and see itself attached to it, pretty cool. Only thing that would be needed to make it seamless is a for a new default arg value identifier like __FILE__ that just translates to whatever the UDA is attached to. I would call it __UDA_ATTACHMENT__. So the above would be translated to:


template someuda(alias attach = __UDA_ATTACHMENT__) {}
@someuda!() int x;


The parens would sadly still be necessary, but then you could do:

import std.range.interfaces;

template StaticInterface(alias Iface, alias attach = __UDA_ATTACHMENT__)
     if(is(Iface == interface))
{
     // See if attach statically implements Iface
}

@StaticInterface!(InputRange)
struct someRange {...}


July 29, 2015
On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:

> So... instead of having traits / concepts, what I wanted from D is to be able to do this:
>
> struct MyRange: isInputRange { ... }
>
> or
>
> struct MyRange: static isInputRange { ... } // that way classes could do this too

+1

Concept like this can help a lot of people will not be scared off by the complexity of Dlang

July 29, 2015
On Tuesday, 28 July 2015 at 13:10:43 UTC, Atila Neves wrote:
> I guess, but not easily. I've written template mixins to do that before and it was awkward.

What was awkward?
July 29, 2015
On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:
> So... instead of having traits / concepts, what I wanted from D is to be able to do this:
>
> struct MyRange: isInputRange { ... }

+1

> or
>
> struct MyRange: static isInputRange { ... } // that way classes could do this too

What about this instead:

    @satisfies(isInputRange) struct MyRange { ... }

which is not as terse, but maybe less confusing, because intuitively ':' could be mistaken to mean 'extends'.

'static' has too many meanings already for my taste. I really don't like it when frequently used keywords are reused to mean different things in slightly different places.

July 29, 2015
On Wednesday, 29 July 2015 at 06:05:37 UTC, Kagamin wrote:
> On Tuesday, 28 July 2015 at 13:10:43 UTC, Atila Neves wrote:
>> I guess, but not easily. I've written template mixins to do that before and it was awkward.
>
> What was awkward?

Writing a generic solution that would work for multiple constraints without code repetition.

Atila
« First   ‹ Prev
1 2 3