2011-06-13 13 views
18

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.

+1

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

+2

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

Respuesta

4
template genericfunctions() { 
    T TestMonoid(T,N = Monoid!T)(T a) { 
    return N.mappend(N.mzero(),a); 
    } 
} 

No hay necesidad de que:

T TestMonoid(T,N = Monoid!T)(T a) { 
    return N.mappend(N.mzero(),a); 
} 

Eso debería ser suficiente. Con esto, no hay necesidad de mixin tampoco.

¿Es posible definir restricciones como las leyes de los funtores con unittest en D?

No del todo seguro de entender lo que está pidiendo, pero se puede definir contraints con funciones de plantilla/clases:

void isEven(T)(T x) if (isIntegral!T) { return x % 2 == 0; } 

Esta plantilla sólo entonces si una instancia T es un tipo entero.

Consulte la sección 'Restricciones de la plantilla' en la parte inferior de la página Templates.

+1

Pensé que sería suficiente, también. Funciona cuando tecleas N por ti mismo, pero si no lo haces, el compilador no puede encontrar la plantilla correcta porque solo se ve en el archivo fuente el método fue definido. Pero gracias por las limitaciones consejo! lo echaré un vistazo – KIMA

+0

@KIMA: ¿Qué versión de DMD estás usando? Funciona bien para mí. –

4

En lugar de responder a su pregunta, ya que eso requeriría comprender lo que ha dicho. Voy a hablar sobre las características de D que estás usando y las que podrían ser útiles para ti.

D no tiene clases de tipo (como usted sabe). En cambio, tiene especialización de tipo (que está utilizando) y template constraints. La especialización tipo viene antes de las restricciones de la plantilla y, de hecho, se puede usar allí.

Una restricción de plantilla le permite requerir ciertas propiedades de un tipo. Encontrará que esto se usa mucho en std.range y hay plantillas que ayudan a escribir tales restricciones en std.traits. Puedo hacer un ejemplo más complicado, pero por ahora, este tipo acepta que convierten a int:

void myFunction(T)(T param) if(is(T:int)) { 
} 
0

¿Es posible definir restricciones como las leyes de functor con unittest en D? Eso seria muy bueno.

Phobos tiene otro concepto en la parte superior del lenguaje, como monos y functors y mónadas. Y eso es Ranges. Ahora, la forma en que Phobos comprueba si un tipo es un rango es definiendo una plantilla que verifica si se pueden invocar ciertas funciones en un tipo. Si estas funciones son genéricas, la respuesta de la plantilla dependerá de que el compilador pueda encontrar un método que coincida con su tipo.

Para refenrence, here's the typecheck for a ForwardRange (puntos de enlace de código con más docs):

template isInputRange(R) 
{ 
    enum bool isInputRange = is(typeof(
    (inout int = 0) 
    { 
     R r = R.init;  // can define a range object 
     if (r.empty) {} // can test for empty 
     r.popFront();  // can invoke popFront() 
     auto h = r.front; // can get the front of the range 
    })); 
} 

Con que se puede crear una restricción de la plantilla de este modo:

template isFunctor(Testant) { 
    enum bool isFunctor = is(typeof(
     () 
     { 
      Testant t = Testant.init;   // can instantiate that type 
      auto result = t.fmap((Testant){}); // can call fmap on it with the type as parameter. 
     } 
} 

Nota del UFCS con fmap anteriormente, fmap aún coincide con tu decalaración.

También tenga en cuenta que podría ser mejor utilizar structs en lugar de clases. Como son tipos de valores y podemos tener Compile Time Function Execution (CTFE) en D, con el uso inteligente de opCall puede usarlos como si fueran funciones.

Cuestiones relacionadas