2011-08-04 19 views
8

estoy tratando de aprender el Y-Combinator mejor (tipo de entiendo que en el esquema) y ponerlo en práctica en D 2.0, y yo estoy fallando bastante miserablemente:Y-combinator en D?

auto fact = delegate(uint delegate(uint) recurse) 
{ 
    return delegate(uint n) 
    { 
     return n > 1 ? n * recurse(n - 1) : 1; 
    }; 
}; 

fact(fact)(5); 

Esto no lo hace trabajo, por la razón obvia de que no puedo pasar fact a fact (¿cuál sería su tipo?). Y además, todavía necesito el nombre fact para pasar a sí mismo, por lo que no funcionaría de todos modos, ¿verdad?

Pero ... ¿cómo hago para implementar el Y-combinator en D?

+0

delegados ya son tipos de referencia, puede soltar 'y'. – BCS

+0

@BCS: Buen punto, originalmente era un método y olvidé eliminarlo. Arreglará. :) – Mehrdad

Respuesta

7

Consulte una explicación detallada here.

+0

Parece que la característica crítica D carece (en comparación con C#) es la capacidad de usar el nombre de un alias delegado dentro de la propia firma ... – Mehrdad

+0

El código de ejemplo en RosettaCode funciona, por lo que es justo decir que D carece de una crítica ¿característica? – gmfawcett

+1

El código de ejemplo en RosettaCode usa moldes de tipo para eludir el problema que es feo. Es posible envolver al delegado dentro de una estructura, ya que las estructuras pueden tener miembros cuyos tipos dependan recursivamente del tipo de estructura. Pero Mehrdad tiene un punto: las declaraciones de alias son actualmente más limitadas de lo necesario ya que el compilador no permite la mayoría de las formas de recursión de alias. (Por ejemplo: struct Foo (T) {Bar * qux;} alias Foo! Int Bar; // error de compilación struct Foo (T) {. Foo!int * qux;} // funciona) – tgehr

3

No sé D, pero con la mayoría de los idiomas tendrá problemas con el tipo de función cuando intente implementar la no recursión (todavía no hay Y-Combinator en su código). Se puede lograr el camino habitual separando los tipos en varias partes y de esta manera obteniendo la recursión en el tipo.

Algunos ejemplos de otros idiomas:

  • En C por lo general se escribe una estructura que contiene el puntero de función. Esta estructura se puede usar como argumento.

  • En OCaml se agregaría un tipo de variante que ajusta el tipo de función. De nuevo, esto permite separar los tipos para que la recursión de tipo sea posible.

Si quiere un ejemplo de otros idiomas, eche un vistazo a this page.

4

Es una limitación conocida de D (y C/C++) que las funciones que se ocupan de referencias automáticas mecanografiadas son imposibles (la última vez que lo comprobé) para declarar.

Me resulta irónico porque equivale a una limitación de sintaxis (la longitud del prototipo de función es infinita) o convención de nomenclatura (el mismo problema pero con el nombre de manipulación) en lugar de cualquier cosa semántica o arquitectónica.

+0

+1 Sí, es un poco tonto, me estaba realmente confundiendo al tratar de envolver mi cerebro sobre cómo pasar una función a sí mismo. Alguna idea si planean arreglar esto? (por ejemplo 'void foo (typeof (foo)) {}' da un error 'forward reference'.) – Mehrdad

+0

@Mahrdad: No que yo sepa. OTOH una envoltura de estructura trivial alrededor del elemento corrige el problema. – BCS

+0

Por cierto, Haskell tampoco hace eso diciendo algo como '" No se puede declarar un tipo de longitud infinita "' – vines

3

Mi combinador Y genérica en D:

import std.stdio, std.algorithm, std.range; 
auto Y(R,T...)(R delegate(T) delegate (R delegate(T)) f){ 
    struct F{R delegate(F,T) f;}; 
    return (R delegate(T)delegate(F) a){return a(F((F b,T i){return f(a(b))(i);})); 
    }((F b){return (T n){return b.f(b,n);};}); 
} 
void main(){ 
    auto factorial=Y((long delegate(long) self){return (long i){return i?self(i-1)*i:1;};}); 
    auto fibonacci=Y((int delegate(int) self){return (int i){return i<2?i:self(i-1)+self(i-2);};}); 
    auto ackermann=Y((long delegate(long,long) self){return (long n,long m){return n?m?self(n-1,self(n,m-1)):self(n-1,1):m+1;};}); 
    writeln(map!factorial(iota(0,20))); 
    writeln(map!fibonacci(iota(0,20))); 
    writeln(map!((a){return ackermann(a%4,a/4);})(iota(0,20))); 
} 

Empecé con una definición recursiva trivial de la función factorial y modificar hasta que mi código se veía así. El problema de tipo infinito se está trabajando con un wrapper de tipo (struct F).