2012-06-27 8 views
15

Es decir, cuando llama a una función con> 1 arity con un solo argumento, debería, en lugar de mostrar un error, curry ese argumento y devolver la función resultante con una disminución de arity. ¿Es posible hacerlo usando las macros de Lisp?¿Es posible implementar el autocurrido a los idiomas de la familia Lisp?

+0

Tendría que implementar un lenguaje escrito encima de Lisp (que es muy fácil con las macros), pero no sería fácil mezclar este lenguaje con el resto del Lisp. –

Respuesta

9

En el Esquema, se pueden ganarse una función utilizando el procedimiento curry:

(define (add x y) 
    (+ x y)) 

(add 1 2)   ; non-curried procedure call 
(curry add)   ; curried procedure, expects two arguments 
((curry add) 1)  ; curried procedure, expects one argument 
(((curry add) 1) 2) ; curried procedure call 

De Raqueta de documentation:

[de curry] devuelve un procedimiento que es una versión curry de proc. Cuando se aplica por primera vez el procedimiento resultante, a menos que se le dé el número máximo de argumentos que puede aceptar, el resultado es un procedimiento para aceptar argumentos adicionales.

Desde aquí se puede aplicar una macro que utiliza automáticamente curry en la definición de nuevos procedimientos, algo como esto:

(define-syntax define-curried 
    (syntax-rules() 
     ((_ (f . a) body ...) 
     (define f (curry (lambda a (begin body ...))))))) 

Ahora será curried la siguiente definición de add:

(define-curried (add a b) 
    (+ a b)) 

add 
> #<procedure:curried> 

(add 1) 
> #<procedure:curried> 

((add 1) 2) 
> 3 

(add 1 2) 
> 3 
+2

Excelentes respuestas en general, pero proporcionó ese pequeño snipplet que resultó ser muy útil, así que no puedo aceptar otra respuesta más que esto. – MaiaVictor

1

Lisp ya tiene Currying funcional:

* (defun adder (n) 
    (lambda (x) (+ x n))) 
ADDER 

http://cl-cookbook.sourceforge.net/functions.html

Esto es lo que estaba leyendo acerca de las macros Lisp: https://web.archive.org/web/20060109115926/http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html

Es posible implementar esto en Lisp puro. También es posible implementarlo usando macros, pero parece que las macros lo harían más confuso para cosas muy básicas.

+1

"arity" es un término común en Prolog. Hay funciones (* predicados *) con el mismo nombre y arias diferentes que se consideran diferentes. –

2

La respuesta corta es sí, aunque no es fácil.

podría implantar esto como una macro que envolvió cada llamada en partial, aunque solo en contexto limitado. Clojure tiene algunas características que lo harían difícil, como las funciones de aria variable y las llamadas dynamit. Clojure carece de un sistema de tipo formal para decidir concretamente cuándo la llamada no puede tener más argumentos y en realidad debe ser llamada.

13

Es posible, pero no es fácil si quiere un resultado útil.

  • Si desea un lenguaje que siempre haga simple currying, entonces la implementación es fácil. Simplemente convierte cada aplicación de más de una entrada en una aplicación anidada, y lo mismo para funciones de más de un argumento. Con las instalaciones de lenguaje Racket's, este es un ejercicio muy simple. (En otros cefaleas, puede obtener un efecto similar mediante alguna macro alrededor del código donde quiera usarlo).

    (Por cierto, tengo un lenguaje encima de Racket que hace justo esto. Se pone completo de lindo lenguajes autocomprimidos, pero no es práctico).

    Sin embargo, no es demasiado útil ya que solo funciona para funciones de un argumento.Podrías hacerlo útil con algunas piratas informáticas, por ejemplo, tratar el resto del sistema de ceceo en tu idioma como idioma extranjero y proporcionar formularios para usarlo. Otra alternativa es proporcionarle a su idioma información acerca de las funciones de ceceo circundantes. Cualquiera de estos requiere mucho más trabajo.

  • Otra opción es simplemente verificar cada aplicación. En otras palabras, se enciende cada

    (f x y z) 
    

    en código que comprueba la aridad de f y creará un cierre si no hay suficientes argumentos. Esto no es demasiado difícil en sí mismo, pero generará un costo indirecto significativo. Podría tratar de utilizar un truco similar de cierta información sobre aries de funciones que usaría en el nivel macro para saber dónde deberían crearse tales cierres, pero eso es difícil esencialmente de la misma manera.

Pero hay un problema mucho más grave, en el alto nivel de lo que quiere hacer. El problema es que las funciones de aria variable simplemente no funcionan bien con el currying automático. Por ejemplo, tener una expresión como:

(+ 1 2 3)

¿Cómo decidir si este debe ser llamado como es, o si debe ser traducido a ((+ 1 2) 3)? Parece que hay una respuesta fácil aquí, pero ¿qué pasa con esto? (Traducir al dialecto de Lisp favorito)

(define foo (lambda xs (lambda ys (list xs ys)))) 

En este caso se puede dividir un (foo 1 2 3) en un número de maneras. Sin embargo, otra cuestión es, ¿qué hacer con algo como:

(list +) 

Aquí tienen + como una expresión, pero podría decidir que esto es lo mismo que aplicar en cero las entradas que se ajusta + s aridad, pero luego ¿cómo se escribe una expresión que evalúa la función de adición? (Nota al margen: ML y Haskell "resuelven" esto al no tener funciones nulary ...)

Algunos de estos problemas se pueden resolver al decidir que cada aplicación "real" debe tener parientes, por lo que un + por sí solo nunca ser aplicado. Pero eso pierde mucho de la ternura de tener un lenguaje autocomprimido, y todavía tiene problemas para resolver ...

1

Según lo observado por Alex W, el libro de recetas Common Lisp does give an example de una función de "curry" para Common Lisp. El ejemplo específico está más abajo en esa página:

(declaim (ftype (function (function &rest t) function) curry) 
     (inline curry)) ;; optional 
(defun curry (function &rest args) 
    (lambda (&rest more-args) 
    (apply function (append args more-args)))) 

Auto-currificación no debería ser tan difícil de poner en práctica, por lo que tomó una grieta en ella.Tenga en cuenta que los siguientes no es ampliamente probado, y no comprueba que no hay demasiados argumentos (la función solo se completa cuando hay ese número o más):

(defun auto-curry (function num-args) 
    (lambda (&rest args) 
    (if (>= (length args) num-args) 
     (apply function args) 
     (auto-curry (apply (curry #'curry function) args) 
        (- num-args (length args)))))) 

parece funcionar, sin embargo:

* (auto-curry #'+ 3) 
#<CLOSURE (LAMBDA (&REST ARGS)) {1002F78EB9}> 

* (funcall (auto-curry #'+ 3) 1) 
#<CLOSURE (LAMBDA (&REST ARGS)) {1002F7A689}> 

* (funcall (funcall (funcall (auto-curry #'+ 3) 1) 2) 5) 
8 

* (funcall (funcall (auto-curry #'+ 3) 3 4) 7) 
14 

Una versión primitiva (no maneja listas lambda completos correctamente, sólo listas de parámetros simples) de un poco de azúcar sintaxis macro sobre lo anterior:

(defmacro defun-auto-curry (fn-name (&rest args) &body body) 
    (let ((currying-args (gensym))) 
    `(defun ,fn-name (&rest ,currying-args) 
     (apply (auto-curry (lambda (,@args) ,@body) 
          ,(length args)) 
       ,currying-args)))) 

parece funcionar, aunque º e necesidad de funcall sigue siendo molesto:

* (defun-auto-curry auto-curry-+ (x y z) 
    (+ x y z)) 
AUTO-CURRY-+ 

* (funcall (auto-curry-+ 1) 2 3) 
6 

* (auto-curry-+ 1) 
#<CLOSURE (LAMBDA (&REST ARGS)) {1002B0DE29}> 
1

Claro, sólo hay que decidir semántica exacta para su idioma, y ​​luego implementar su propio cargador de lo que se traducirá los archivos de origen en el lenguaje de implementación.

Puede p. Ej. traduzca cada función de usuario llame (f a b c ... z) al (...(((f a) b) c)... z), y cada (define (f a b c ... z) ...) a (define f (lambda(a) (lambda(b) (lambda(c) (... (lambda(z) ...) ...))))) encima de un Esquema, para tener un Esquema de auto-currying (eso prohibiría funciones varargs por supuesto).

También deberá definir sus propias primitivas, convirtiendo las funciones varargs, como p. Ej. (+) a binario, y convertir sus aplicaciones a usar fold p. (+ 1 2 3 4) ==>(fold (+) (list 1 2 3 4) 0) o simplemente haciendo llamadas como (+ 1 2 3 4) ilegal en su nuevo idioma, esperando que su usuario escriba fold formularios por sí mismo.

Eso es lo que quise decir con "decidir ... semántica para su idioma".

El cargador puede ser tan simple como envolver el contenido del archivo en una llamada a una macro - que luego tendría que implementar, según su pregunta.

Cuestiones relacionadas