Jump to page: 1 2
Thread overview
Is there any chance to introduce concepts/traits/typeClasses in dlang?
Dec 15, 2019
sighoya
Dec 15, 2019
rikki cattermole
Dec 15, 2019
mipri
Dec 15, 2019
rikki cattermole
Dec 15, 2019
sighoya
Dec 16, 2019
rikki cattermole
Dec 16, 2019
sighoya
Dec 16, 2019
rikki cattermole
Dec 16, 2019
sighoya
Dec 16, 2019
sighoya
Dec 16, 2019
jmh530
Dec 16, 2019
sighoya
Dec 15, 2019
sighoya
Dec 15, 2019
sighoya
December 15, 2019
I know there is already a package available from atila neves about concepts, but despite being a neat workaround, it delivers not the full opportunities we know from type classes.

Why?
TypeClasses aren't a static solution only, they can also be used as existentials allowing dynamic dispatch implying that you can change the underlying implementation at runtime:
function(Concept c):Concept{...}

Further, implementing concepts over inner fields is limited, usually assoc functions are used (UFCS in case of D) which may can be covered with the concepts package of atila over dlang's compiletime reflection library "traits".
However, how instance resolution works then? Functions associated to types are local only, so if we import the concept from another module how can we check satisfyability if the needed ufcs/associated functions aren't imported likewise to the concept?

I know, introducing a new entity to existing entities like interfaces unnecessarily increases the complexity but how about reusing existing interfaces for that purpose?

For instance:

interface A
{
void method1();
}

interface B
{
void method2();
}

class C implements A //structural implements (valid D)
{
void method1() {...}
}

implement B for C //implement is a block keyword like class for posthoc implementation
{
method2(){...}
}

Another solution would be a pseudoclass:

class D implements B for C
{
method2(){...}
}

Now passing a C to B requires not only too look for C's definition but also for instances either globally in the project (preferred) or with special import for instances.

Another point to consider struct implementations via autoboxing, is it that hard to implement?
Also allowing fields in interfaces would complete the usefulness of concepts.

A possible look how flexible it can be taken from the nim language
Link:
https://nim-lang.org/docs/manual_experimental.html#concepts

I donna want to rant about D, I love it, really. But introducing classes separate to interfaces irks me and was in my eyes a failure from Java and all languages which follow them.

Why?
Because it introduces a biworld, both concepts overlap and you have to decide which to choose which is further complicated by availability of abstract classes, so why not unify all of them into a single concept?
Class methods were always default methods akin to default methods in interfaces and
collision by multiple inheritance wasn't ever an issue which can be easily solved by scoping the super interfaces.
Another biworld are final classes and structs which are seemingly equal in their purpose, from a type theoretic viewpoint they represent the same type, so why have to versions of the same abstraction, choosing only structs as value or reference type would excitingly solve the issue.

Nevertheless, we can't changed everything in a backward-compatible way. I hope some of you are happy to discuss ideas about concepts in this thread.
December 16, 2019
I have already gone down this path.

My design for named arguments was step one, but sadly DIP 1020 is dead.

My preference is towards signatures from ML.

Pretty simple concept, but in practice it is just as complex as classes are and would be just as big.

I don't think Walter has made a decision about this, but for reference: if a language had a similar scope to D and it supported them, I would probably jump. They would be a major improvement over D today for documenting API's especially with meta-programming considered.
December 15, 2019
On Sunday, 15 December 2019 at 16:11:31 UTC, sighoya wrote:
> TypeClasses aren't a static solution only, they can also be
> used as existentials allowing dynamic dispatch
...
> A possible look how flexible it can be taken from the nim
> language
> Link:
> https://nim-lang.org/docs/manual_experimental.html#concepts

From that page:

  The concept is a match if:
  - all of the expressions within the body can be compiled for the tested type
  - all statically evaluable boolean expressions in the body must be true

Looks to me like this is just a way of attaching D's static
constraints to a type, so that

  void push(T)(Stack!T stack, T item) { }

becomes (something like, for inference's sake S shouldn't be
separate I think):

  void push(S,T)(S!T stack, T item) if (a bunch of !S tests) { }

What does this have to do with dynamic dispatch? How is this
not already something you can do with an isStack!S test?

> Nevertheless, we can't changed everything in a backward-compatible way. I hope some of you are happy to discuss ideas about concepts in this thread.

I don't understand what you're trying to do, and these A, B, C,
D examples take a lot effort to read, vs. Comparable and Stack
examples.

What I'd prefer is

  // valid current-D example:

  ...

  // but look at (how verbose this is | how expensive this is |
  // how much effort it'd take to adapt this in Y direction |
  // &c)!

vs.

  // valid improved-D example:

December 16, 2019
The easiest D example for this family of language features is ranges IMO.

Turn:

auto map(alias func, IR)(IR input) if (...) {
	struct Voldemort {
		...
	}

	return ...;
}

Into:

auto:InputRange map(alias func, IR:InputRange)(IR input) if (...) {
	struct Voldemort {
		...
	}

	return ...;
}

That is the static introspection capabilities.
For dynamic:

// typeof(input) is still templated, its just hidden, since only the template arguments need deducing
InputRange!ElementType map(alias func)(auto:InputRange input) if (...) {
	struct Voldemort {
		...
	}

	return ...; // auto construction of an InputRange instance with deduction of what ElementType is.
}
December 15, 2019
> Looks to me like this is just a way of attaching D's static
> constraints to a type, so that
>
>   void push(T)(Stack!T stack, T item) { }
>
> becomes (something like, for inference's sake S shouldn't be
> separate I think):
>

>   void push(S,T)(S!T stack, T item) if (a bunch of !S tests) { }
>

The point is that this only works for:

1.) static dispatch (compile time dispatch)
2.) for structural matches (is there any kind to constraint a type on its ufcs/assoc methods?), structural methods can't be added after the type is defined, however ufcs/assoc/extension methods can be defined later and pulled automatically into function scope of the method taking the implementing type of the concept:

void fun(C:Concept)(C c)
<==>
void fun(C)(C c) if (C has all ufcs/assoc functions listed in the concept C)

How to express the latter?

> What does this have to do with dynamic dispatch? How is this
> not already something you can do with an isStack!S test?

Dynamic dispatch mean you have an runtime uncertainty of the implementing type of a concept like the implementing type of a interface, so you can write:

fun(Concept c) akin to fun(Interface i) which is different to:

fun(C:Concept)(C c)

as the latter method-set for the implementing type is passed at compile time and for the former at runtime.




December 15, 2019
> That is the static introspection capabilities.
> For dynamic:
>
> // typeof(input) is still templated, its just hidden, since only the template arguments need deducing
> InputRange!ElementType map(alias func)(auto:InputRange input) if (...) {
> 	struct Voldemort {
> 		...
> 	}
>
> 	return ...; // auto construction of an InputRange instance with deduction of what ElementType is.
> }

Im not quite aware of the meaning of auto:InputRange but in my eyes it is static dispatching too, I find it equivalent too:

InputRange!ElementType map(alias func,ElemType)(InputRange!ElemType input){...}

December 15, 2019
To clarify a bit:

Passing a struct containing all the methods in the constraint:

fun(Struct s) if(s.containsMethodsOf(Concept))

is no problem, as the instance (the set of required methods in the if constraint is delivered by the struct itself when it is passed as argument to fun), but for the case of type classes/concepts, the instance is separated from the passed type and must be implicit passed by the compiler, you need compiler magic for that akin to Rust or you do it manually akin to the ugly variant in Scala, even Nim uses magic for this.

December 16, 2019
On 16/12/2019 9:07 AM, sighoya wrote:
>> That is the static introspection capabilities.
>> For dynamic:
>>
>> // typeof(input) is still templated, its just hidden, since only the template arguments need deducing
>> InputRange!ElementType map(alias func)(auto:InputRange input) if (...) {
>>     struct Voldemort {
>>         ...
>>     }
>>
>>     return ...; // auto construction of an InputRange instance with deduction of what ElementType is.
>> }
> 
> Im not quite aware of the meaning of auto:InputRange but in my eyes it is static dispatching too, I find it equivalent too:
> 
> InputRange!ElementType map(alias func,ElemType)(InputRange!ElemType input){...}

Whatever map returns, can be initialized to the InputRange signature.
That is what auto:Signature means. Where Signature can be although doesn't have to be template initialized.

I think this is quite nice syntax, as it works in the function parameters and it is easily recognized by the parser because keyword auto then ':' then some type.

What you changed it to, is using runtime dispatch instead of static.
Another form I've come up with for static is ``is(T : Signature)``. With same meaning as ``auto:Signature`` but requires template constraints to mean the same thing.
December 16, 2019
On Monday, 16 December 2019 at 02:35:50 UTC, rikki cattermole wrote:
> On 16/12/2019 9:07 AM, sighoya wrote:
>>> That is the static introspection capabilities.
>>> For dynamic:
>>>
>>> // typeof(input) is still templated, its just hidden, since only the template arguments need deducing
>>> InputRange!ElementType map(alias func)(auto:InputRange input) if (...) {
>>>     struct Voldemort {
>>>         ...
>>>     }
>>>
>>>     return ...; // auto construction of an InputRange instance with deduction of what ElementType is.
>>> }
>> 
>> Im not quite aware of the meaning of auto:InputRange but in my eyes it is static dispatching too, I find it equivalent too:
>> 
>> InputRange!ElementType map(alias func,ElemType)(InputRange!ElemType input){...}
>
> Whatever map returns, can be initialized to the InputRange signature.
> That is what auto:Signature means. Where Signature can be although doesn't have to be template initialized.
>
> I think this is quite nice syntax, as it works in the function parameters and it is easily recognized by the parser because keyword auto then ':' then some type.
>
> What you changed it to, is using runtime dispatch instead of static.
> Another form I've come up with for static is ``is(T : Signature)``. With same meaning as ``auto:Signature`` but requires template constraints to mean the same thing.

Ah okay, so this is a specialization of the auto kw which is generic T implying T:Signature
December 17, 2019
On 17/12/2019 12:13 AM, sighoya wrote:
> On Monday, 16 December 2019 at 02:35:50 UTC, rikki cattermole wrote:
>> On 16/12/2019 9:07 AM, sighoya wrote:
>>>> That is the static introspection capabilities.
>>>> For dynamic:
>>>>
>>>> // typeof(input) is still templated, its just hidden, since only the template arguments need deducing
>>>> InputRange!ElementType map(alias func)(auto:InputRange input) if (...) {
>>>>     struct Voldemort {
>>>>         ...
>>>>     }
>>>>
>>>>     return ...; // auto construction of an InputRange instance with deduction of what ElementType is.
>>>> }
>>>
>>> Im not quite aware of the meaning of auto:InputRange but in my eyes it is static dispatching too, I find it equivalent too:
>>>
>>> InputRange!ElementType map(alias func,ElemType)(InputRange!ElemType input){...}
>>
>> Whatever map returns, can be initialized to the InputRange signature.
>> That is what auto:Signature means. Where Signature can be although doesn't have to be template initialized.
>>
>> I think this is quite nice syntax, as it works in the function parameters and it is easily recognized by the parser because keyword auto then ':' then some type.
>>
>> What you changed it to, is using runtime dispatch instead of static.
>> Another form I've come up with for static is ``is(T : Signature)``. With same meaning as ``auto:Signature`` but requires template constraints to mean the same thing.
> 
> Ah okay, so this is a specialization of the auto kw which is generic T implying T:Signature

Yeah, basically it tells the reader that the return value is guaranteed to be X. A small but what I think would be a very nice documentation addition especially for Phobos which is hard to get into.
« First   ‹ Prev
1 2