2012-06-17 14 views

Respuesta

14

Este estilo de programación es posible en CL en principio, pero, al ser un Lisp-2, hay que agregar varios #' sy funcall s. Además, a diferencia de Haskell, por ejemplo, las funciones no se curry en CL, y no hay una aplicación parcial implícita. En general, creo que ese estilo no sería muy idiomático CL.

Por ejemplo, se podría definir una aplicación parcial y la composición de esta manera:

(defun partial (function &rest args) 
    (lambda (&rest args2) (apply function (append args args2)))) 

(defun comp (&rest functions) 
    (flet ((step (f g) (lambda (x) (funcall f (funcall g x))))) 
    (reduce #'step functions :initial-value #'identity))) 

(Estos son sólo ejemplos rápidos que nos prepararon rápidamente - en realidad no están probados o bien pensada para diferentes casos de uso.)

Con ellos, algo así como map ((*2) . (+1)) xs en Haskell se convierte en:

CL-USER> (mapcar (comp (partial #'* 2) #'1+) '(1 2 3)) 
(4 6 8) 

El ejemplo sum:

CL-USER> (defparameter *sum* (partial #'reduce #'+)) 
*SUM* 
CL-USER> (funcall *sum* '(1 2 3)) 
6 

(En este ejemplo, se puede también ajustar la función celular de un símbolo en lugar de almacenar la función en la celda de valor, con el fin de conseguir alrededor de la funcall.)

En Emacs Lisp, por el camino, la aplicación parcial está incorporada como apply-partially.

En Qi/Shen, funciones están al curry, y la aplicación parcial implícita (cuando las funciones son llamadas con un argumento) se apoya:

(41-) (define comp F G -> (/. X (F (G X)))) 
comp 

(42-) ((comp (* 2) (+ 1)) 1) 
4 

(43-) (map (comp (* 2) (+ 1)) [1 2 3]) 
[4 6 8] 

También hay azúcar roscado sintáctica en Clojure que da una sensación similar de "pipelining":

user=> (-> 0 inc (* 2)) 
2 
2

Sí, esto es posible en general con las funciones correctas. Por ejemplo, aquí hay un ejemplo de la aplicación de la raqueta sum de la página de Wikipedia:

#lang racket 
(define sum (curry foldr + 0)) 

Dado que los procedimientos no están al curry por defecto, que ayuda a utilizar curry o escribir sus funciones en un estilo de forma explícita al curry. Podría abstraer esto con una nueva macro define que usa currying.

+2

La pregunta es sobre Common Lisp, esta respuesta es correcta para Scheme, pero no para CL –

+1

Bueno, en realidad la pregunta era sobre Lisp en general. Agregué CL como una etiqueta ya que es el dialecto Lisp con el que estoy más familiarizado, pero esta respuesta que usa Scheme también es útil. – paldepind

3

SÍ, es posible y @danlei ya lo explicó muy bien. Voy a agregar algunos ejemplos del libro ANSI Common Lisp por Paul Graham, capítulo 6.6 de los constructores de función:

puede definir un constructor función como esta:

(defun compose (&rest fns) 
    (destructuring-bind (fn1 . rest) (reverse fns) 
    #'(lambda (&rest args) 
     (reduce #'(lambda (v f) (funcall f v)) 
       rest 
       :initial-value (apply fn1 args))))) 

(defun curry (fn &rest args) 
    #'(lambda (&rest args2) 
     (apply fn (append args args2)))) 

y utilizar de esta manera

(mapcar (compose #'list #'round #'sqrt) 
     '(4 9 16 25)) 

vuelve

((2) (3) (4) (5)) 

La llamada compose función:

(compose #'a #'b #'c) 

se equlvalent a

#'(lambda (&rest args) (a (b (apply #'c args)))) 

Esto significa componer puede tomar cualquier número de argumentos, sí.

hacer una función que se suman a 3 argumento:

(curry #'+ 3) 

Ver más en el libro.

+2

pero no tiene mucho sentido. Esto lleva a un código malo Más difícil de leer y depurar. Además, casi todos los compiladores CL generarán código lento para eso (listas de argumentos consp, etc.). –

+0

@RainerJoswig Tienes razón. Esto podría mostrar cuán flexible es CL y qué cierre nos puede ayudar ... o_O – juanitofatas

+2

Su ejemplo 'compose' también funciona con mi versión. Acabo de tener un pequeño error de copiar y pegar o un error tipográfico en 'comp' (el orden incorrecto de las llamadas de función en' step', versión de trabajo en el REPL) que está arreglado ahora. Además, creo que Rainer tiene razón: aunque está claro que es bueno poder hacer esto en CL, no es muy idiomático. Haskell se presta mucho más a este estilo de programación.(El estilo de Graham, por cierto, es considerado bastante idiosincrásico por muchos programadores de CL). – danlei

7

Se podría utilizar algo como (esto es hace un poco más de -> en Clojure):

(defmacro -> (obj &rest forms) 
    "Similar to the -> macro from clojure, but with a tweak: if there is 
    a $ symbol somewhere in the form, the object is not added as the 
    first argument to the form, but instead replaces the $ symbol." 
    (if forms 
     (if (consp (car forms)) 
      (let* ((first-form (first forms)) 
       (other-forms (rest forms)) 
       (pos (position '$ first-form))) 
      (if pos 
       `(-> ,(append (subseq first-form 0 pos) 
           (list obj) 
           (subseq first-form (1+ pos))) 
        ,@other-forms) 
       `(-> ,(list* (first first-form) obj (rest first-form)) 
        ,@other-forms))) 
      `(-> ,(list (car forms) obj) 
       ,@(cdr forms))) 
     obj)) 

(hay que tener cuidado para exportar también el símbolo $ del paquete en que se colocan -> - vamos a llamar a ese paquete tacit - y poner tacit en el use cláusula de cualquier paquete donde va a utilizar ->, por lo -> y $ se heredan)

Ejemplos de uso:

(-> "TEST" 
    string-downcase 
    reverse) 

(-> "TEST" 
    reverse 
    (elt $ 1)) 

Esto es más como F # 's |> (y el tubo de la cáscara) que Haskell de ., pero son más o menos lo mismo (yo prefiero |>, pero esto es una cuestión de gusto personal).

para ver lo que está haciendo ->, simplemente macroexpand el último ejemplo tres veces (en el limo, esto se logra al poner el cursor en la primera ( en el ejemplo y escribir C-c RET tres veces).