www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - An example of typeclasses/traits/protocols/concepts in D with

reply sighoya <sighoya gmail.com> writes:
```D
import std.stdio;

public interface Number(T)
{
     T add(T)(T n1,T n2);
     T sub(T)(T n1,T n2);
     T mul(T)(T n1,T n2);
     T div(T)(T n1,T n2);
}

public class DoubleNumber:Number!double
{
     static double add(double n1,double n2)
     {
         return n1+n2;
     }
     static double sub(double n1,double n2)
     {
         return n1-n2;
     }
     static double mul(double n1,double n2)
     {
         return n1*n2;
     }
     static double div(double n1,double n2)
     {
         return n1/n2;
     }
}


class IntNumber:Number!int
{
     static int add(int n1,int n2)
     {
         return n1+n2;
     }
     static int sub(int n1,int n2)
     {
         return n1-n2;
     }
     static int mul(int n1,int n2)
     {
         return n1*n2;
     }
     static int div(int n1,int n2)
     {
         return n1/n2;
     }
}

//Typeclasss instances
alias Instance(T:Number!int) = IntNumber;
alias Instance(T:Number!double) = DoubleNumber;


T squareCompiletime(T, alias Implementor = Instance!(Number!T))(T 
number)
{
     return Implementor.mul(number,number);
}

T squareRuntime(T)(T number,Instance!(Number!T) instance = new 
Instance!(Number!T))
{
     return instance.mul(number,number);
}

int main()
{
     writeln(squareCompiletime(3));
     writeln(squareRuntime(3));
     writeln(squareCompiletime(3.0));
     writeln(squareRuntime(3.0));
     return 0;
}
```

Some things that irk me:
1.) Can't we import aliases from other modules or am I just too 
dumb to see how?
2.) it would be nice to unscope the methods of a type, e.g. `T 
square(T, unscope alias Implementor = ...)` s.t. we don't have to 
prefix it all the time with Implementor, the same for the runtime 
version of square.

This should also work with operators, I've left them out here.

Clearly, language site implicit instance resolution is often more 
convenient, but D people like it more explicit. Moreover, 
language site instance resolution has to be implemented carefully 
to choose the appropriate instances with regard to the current 
scope (a.k.a global coherence problem) and most languages simply 
don't get that right, we can do it better, manually :).

I think it is also possible to require typeclass dependencies 
like in Rust, e.g. if A gets implemented then B also, or each 
Number must be Equatable:

`public interface Number(T) where Instance!(Eq!T)`
Dec 23 2020
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Wednesday, 23 December 2020 at 14:20:39 UTC, sighoya wrote:
 1.) Can't we import aliases from other modules or am I just too 
 dumb to see how?
plain import includes aliases....
 2.) it would be nice to unscope the methods of a type
that's what the `with` keyword does T square(T, alias Implementor...) { with(Implementor) { // here } }
Dec 23 2020
next sibling parent reply sighoya <sighoya gmail.com> writes:
On Wednesday, 23 December 2020 at 14:23:34 UTC, Adam D. Ruppe 
wrote:
 On Wednesday, 23 December 2020 at 14:20:39 UTC, sighoya wrote:
 1.) Can't we import aliases from other modules or am I just 
 too dumb to see how?
plain import includes aliases....
Strange, because importing alias Instance(T:Number!double) = DoubleNumber; doesn't work it complains with errors.
 2.) it would be nice to unscope the methods of a type
that's what the `with` keyword does T square(T, alias Implementor...) { with(Implementor) { // here } }
Nice, I wasn't aware of this, is akin to that possible for runtime objects?
Dec 23 2020
parent sighoya <sighoya gmail.com> writes:
On Wednesday, 23 December 2020 at 14:56:19 UTC, sighoya wrote:
 On Wednesday, 23 December 2020 at 14:23:34 UTC, Adam D. Ruppe 
 wrote:
 On Wednesday, 23 December 2020 at 14:20:39 UTC, sighoya wrote:
 1.) Can't we import aliases from other modules or am I just 
 too dumb to see how?
plain import includes aliases....
Strange, because importing alias Instance(T:Number!double) = DoubleNumber; doesn't work it complains with errors.
 2.) it would be nice to unscope the methods of a type
that's what the `with` keyword does T square(T, alias Implementor...) { with(Implementor) { // here } }
Nice, I wasn't aware of this, is akin to that possible for runtime objects?
Ah nice, I see it, theats great!
Dec 23 2020
prev sibling parent sighoya <sighoya gmail.com> writes:
The error was:
Error: template instance Instance!(Number!double) does not match 
template declaration Instance(T : Number!int)
Dec 23 2020
prev sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
I prefer the original name, signatures and to ditch the nastiness that 
is classes (which do everything wrong for best practice OOP).

https://gist.github.com/rikkimax/826e1c4deb531e8dd993815bf914acea#signatures
Dec 23 2020
parent sighoya <sighoya gmail.com> writes:
On Wednesday, 23 December 2020 at 23:21:47 UTC, rikki cattermole 
wrote:
 I prefer the original name, signatures and to ditch the 
 nastiness that is classes (which do everything wrong for best 
 practice OOP).

 https://gist.github.com/rikkimax/826e1c4deb531e8dd993815bf914acea#signatures
Could you elaborate a bit more about the signature details, when does an implementation (is it any kind of type except signature?) conform to the signature. Conformity by structurality or by an indirect instance? The former variant is known as structural typing, the best type system known for this is ocaml, and they already use the name `signature` for it, so it would already fit. See: https://www.cs.cornell.edu/courses/cs3110/2020fa/textbook/modules/signatures.html Note, there is an experimental feature to allow for structuralism with structs and interfaces See: https://dlang.org/phobos/std_experimental_typecons.html I want that too together with an operator opImplicitCoerce or opImplicitConvert (that was already proposed by someone else here) allowing implicit coercion/conversion when passing structs to callees expecting conforming interfaces. An implicit operator is much more powerful than alias this which is coupled to an source type, the operator is decoupled. I don't know what the state is with this extension and why it is over years still experimental... The latter is known as typeclasses/traits/protocols. It is more powerul as a type can match even when their names doesn't equal the names in the typeclass, but the drawback is you have to pass an indirect impl to each function using typeclasses. I have proposed a way to do it semi-automatically either by templates or by a default value s.t. not at every time at the callsite an implementation has to be passed into the callee. Note, with an implicit conversion operator, we could also pass implicitly the typeclass instance in the conversion operation at the call site but that would ,sadly, only cover the non generic case. I think that there is a big resistance in the D community regarding the introduction of new concepts, therefore reusing interfaces may be an acceptable way to go eventually by supporting to pass instances implicitly in the generic case, I don't know. I think we would also need to enhance the overload resolution for concrete types s.t. the method is preferred which matches either exactly or there is an implicit order of conversions yielding the narrowest method, i.e. the method for which all types parameters of it are convertible in order to all other types of each method. It would reduce ambiguity akin to Walthers fear about multiple inheritance a lot. And if we have ambiguity we just add another method which intersects all the parameter types of all the methods in order if the types are interfaces or classes or any other existential, otherwise we have to add a method with an exact match. It would require intersection types though, but I think we can construct them by templates?
Dec 24 2020