Soy nuevo en D, y estoy buscando una buena manera de programar con clases tipo Haskell, p. Ej. Functors, Monoids, etc. en D.¿Se pueden usar los rasgos en D para las clases de tipo?
¿Algo así se implementa en Tango o Phobos?
He oído hablar de rasgos que permiten la comprobación de tipos en tiempo de compilación para ciertas propiedades. ¿Pueden ser utilizados para clases de tipo?
que he probado un poco con especialización de plantilla y llegar a esto:
// Monoid.d
// generic Monoid gets called when there is no instance of Monoid for Type T
class Monoid(T) {
pragma(msg, "Type is not a Monoid");
}
// Monoid instance for double
class Monoid(T:double) {
static T mzero() { return 0; }
static T mappend(T a, T b) { return a + b;}
}
// Monoid instance for int
class Monoid(T:int) {
static T mzero() { return 0; }
static T mappend(T a, T b) { return a + b;}
}
Un algoritmo genérico cuyo parámetro de tipo debe ser un Monoid podría entonces ser expresada como:
template genericfunctions() {
T TestMonoid(T,N = Monoid!T)(T a) {
return N.mappend(N.mzero(),a);
}
}
Sin embargo, si desea omitir los parámetros de la plantilla, debe importar todas las instancias Monoid necesarias y mezclar la plantilla genericfunctions
.
import Monoid;
import std.stdio;
import std.conv;
mixin genericfunctions;
void main() {
writefln(to!string(TestMonoid(3)));
writefln(to!string(TestMonoid(3.3243)));
}
Ahora puede utilizar ints y dobles como Monoids.
Sin embargo las cosas se ponen más complejo cuando se tiene un tipo de clase como Functor cuyas instancias son en sí genérica:
module Functors;
// generic Functor like generic Monoid
class Functor(alias T, A) {
pragma(msg,"Not an instance of Functor");
}
// very simple container to demonstrate functors behavior
class FunctorTest(A) {
public A a;
this(A a) {
this.a = a;
}
}
// instance of Functor for FunctorTest!A
class Functor(alias T:FunctorTest,A) {
static T!B fmap(B)(T!A a, B delegate(A) fn) {
return new T!B(fn(a.a));
}
}
Un algoritmo se vería así:
template genericfunctions() {
T TestMonoid(T,N = Monoid!T)(T a) {
return N.mappend(N.mzero(),a);
}
// F is the Functor, A the functors type before,
// B the functors Type after, N is the instance of Functor
F!B fmap(alias F,A,B,N=Functor!(F,A))(F!A a, B delegate(A) fn) {
return N.fmap!B(a,fn);
}
}
Por suerte, se puede omitir la cuatro parámetros de plantilla cuando lo usa:
mixin genericfunctions;
void main() {
auto a = new FunctorTest!int(3);
auto b = fmap(a,(int b) {return b+ 0.5;});
writefln(to!string(b.a));
}
Pero cuando quiera para usar otra instancia de Functor para Type, debe especificar los 4 parámetros de tipo de fmap. ¿Hay alguna manera en la que solo se necesita especificar la instancia y los otros parámetros se pueden deducir a partir de esto?
¿Existe una alternativa a la solución mixin torpe?
¿Hay otras desventajas de este enfoque que no veo?
¿Qué pasa con otras formas?
Gracias por leer hasta aquí y por tomarse el tiempo para pensar y responder :)
Editar:
¿Es posible definir restricciones como las leyes de los funtores con unittest en D? Eso seria muy bueno.
La manera menos torpe puede ser dejar de imitar las construcciones de FP puro en un lenguaje que no sea FP. Dependiendo de lo que trates de hacer, podría ser (o no) más fácil. – BCS
D es un lenguaje de múltiples paradigmas y tiene un muy buen soporte para FP, como funciones de primera clase y funciones PURE que solo se encuentran en Haskell hasta ahora. Las clases de tipos son una característica realmente poderosa ya que permiten programar en abstracción. Su ventaja sobre la herencia es que puede agregar propiedades a los tipos existentes. Si tuvieras una interfaz Monoid, no hubieras podido usar int como un monoide ni otro tipo construido antes. Y creo que es posible tenerlos en D.Aquí es difícil de implementar en el lado de la función, pero en el lado de la persona que llama no lo reconoce. – KIMA