¿Qué hace que las macros de Lisp sean tan especiales?


297

Al leer los ensayos de Paul Graham sobre lenguajes de programación, uno pensaría que las macros de Lisp son el único camino a seguir. Como desarrollador ocupado, trabajando en otras plataformas, no he tenido el privilegio de usar macros Lisp. Como alguien que quiere entender el rumor, explique qué hace que esta característica sea tan poderosa.

También relacione esto con algo que entendería de los mundos del desarrollo de Python, Java, C # o C.


2
Por cierto, hay un procesador de macro estilo LISP para C # llamado LeMP: ecsharp.net/lemp ... JavaScript también tiene uno llamado Sweet.js: sweetjs.org
Qwertie

@Qwertie ¿Sweetjs incluso funciona en estos días?
fredrik.hjarner

No lo he usado, pero la confirmación más reciente fue hace seis meses ... ¡lo suficientemente bueno para mí!
Qwertie

Respuestas:


308

Para dar una respuesta breve, las macros se utilizan para definir extensiones de sintaxis de lenguaje a Common Lisp o Lenguajes específicos de dominio (DSL). Estos idiomas están integrados directamente en el código Lisp existente. Ahora, los DSL pueden tener una sintaxis similar a Lisp (como el Prolog Interpreter de Peter Norvig para Common Lisp) o completamente diferente (por ejemplo, Infix Notation Math para Clojure).

Aquí hay un ejemplo más concreto:
Python tiene comprensiones de listas integradas en el lenguaje. Esto proporciona una sintaxis simple para un caso común. La línea

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

produce una lista que contiene todos los números pares entre 0 y 9. De vuelta en Python 1.5 días no había tal sintaxis; usarías algo más como esto:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

Ambos son funcionalmente equivalentes. Invoquemos nuestra suspensión de la incredulidad y pretendamos que Lisp tiene una macro de bucle muy limitada que solo hace iteraciones y no es una manera fácil de hacer el equivalente de las comprensiones de listas.

En Lisp podrías escribir lo siguiente. Debo señalar que este ejemplo artificial se elige para ser idéntico al código de Python, no es un buen ejemplo de código Lisp.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

Antes de continuar, debería explicar mejor qué es una macro. Es una transformación realizada en código por código. Es decir, un fragmento de código, leído por el intérprete (o compilador), que toma el código como argumento, manipula y devuelve el resultado, que luego se ejecuta in situ.

Por supuesto, eso es mucho escribir y los programadores son flojos. Entonces podríamos definir DSL para hacer comprensiones de listas. De hecho, ya estamos usando una macro (la macro de bucle).

Lisp define un par de formas de sintaxis especiales. La cita ( ') indica que el siguiente token es literal. El cuasiquote o backtick ( `) indica que el siguiente token es un literal con escapes. Los escapes son indicados por el operador de coma. El literal '(1 2 3)es el equivalente de Python [1, 2, 3]. Puede asignarlo a otra variable o usarlo en su lugar. Puede pensar `(1 2 ,x)como el equivalente de Python [1, 2, x]donde xes una variable previamente definida. Esta notación de la lista es parte de la magia que entra en las macros. La segunda parte es el lector Lisp que sustituye inteligentemente las macros por el código, pero que se ilustra mejor a continuación:

Entonces podemos definir una macro llamada lcomp(abreviatura para la comprensión de la lista). Su sintaxis será exactamente como la pitón que usamos en el ejemplo [x for x in range(10) if x % 2 == 0] :(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

Ahora podemos ejecutar en la línea de comando:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

Bastante ordenado, ¿eh? Ahora no se detiene ahí. Tienes un mecanismo, o un pincel, si quieres. Puede tener cualquier sintaxis que pueda desear. Como Python o la withsintaxis de C # . O la sintaxis LINQ de .NET. Al final, esto es lo que atrae a las personas a Lisp: la máxima flexibilidad.


51
+1 para implementar la comprensión de listas en Lisp, porque ¿por qué no?
ckb

99
En realidad @ckb LISP ya tiene una macro lista por comprensión en la biblioteca estándar: (loop for x from 0 below 10 when (evenp x) collect x), más ejemplos aquí . Pero, de hecho, el bucle es "solo una macro" (en realidad lo volví a implementar desde cero hace algún tiempo )
Suzanne Dupéron

8
Sé que no está relacionado, pero me pregunto acerca de la sintaxis y cómo funciona realmente el análisis ... Digamos que llamo a lcomp de esa manera (cambiando el elemento de "for" a "azertyuiop"): (lcomp x azertyuiop x in ( rango 10) si (= (% x 2) 0)) ¿la macro seguirá funcionando como se esperaba? ¿O se usa el parámetro "for" en el bucle de modo que debe ser la cadena "for" cuando se llama?
Dader

2
@dader Entonces, loop es en realidad otra macro y for es un símbolo especial utilizado en esa macro. Podría haber definido fácilmente la macro sin la macro de bucle. Sin embargo, si lo hubiera hecho, la publicación habría sido mucho más larga. La macro de bucle y toda su sintaxis se define en CLtL .
gte525u

3
Una cosa que me confunde con las macros de otros idiomas es que sus macros están limitadas por la sintaxis del idioma del host. ¿Pueden las macros Lispy interpretar la sintaxis no Lispy? Me refiero a imaginar crear una sintaxis similar a haskell (sin paranthese) e interpretarla usando macros Lisp. ¿Es esto posible y cuáles son las ventajas y desventajas de usar macros en comparación con usar un lexer y un analizador directo?
CMCDragonkai

105

Encontrarás un debate exhaustivo sobre lisp macro aquí .

Un subconjunto interesante de ese artículo:

En la mayoría de los lenguajes de programación, la sintaxis es compleja. Las macros tienen que separar la sintaxis del programa, analizarla y volver a montarla. No tienen acceso al analizador del programa, por lo que tienen que depender de la heurística y las mejores suposiciones. A veces su análisis de la tasa de corte es incorrecto, y luego se rompen.

Pero Lisp es diferente. Macros Lisp hacer tener acceso al analizador, y es un programa de análisis muy simple. Una macro Lisp no recibe una cadena, sino un fragmento de código fuente preparado previamente en forma de lista, porque la fuente de un programa Lisp no es una cadena; Es una lista. Y los programas Lisp son realmente buenos para separar listas y volver a armarlas. Lo hacen de manera confiable, todos los días.

Aquí hay un ejemplo extendido. Lisp tiene una macro, llamada "setf", que realiza la asignación. La forma más simple de setf es

  (setf x whatever)

que establece el valor del símbolo "x" al valor de la expresión "lo que sea".

Lisp también tiene listas; puede usar las funciones "car" y "cdr" para obtener el primer elemento de una lista o el resto de la lista, respectivamente.

¿Qué sucede si desea reemplazar el primer elemento de una lista con un nuevo valor? Hay una función estándar para hacerlo, e increíblemente, su nombre es aún peor que "auto". Es "rplaca". Pero no tienes que recordar "rplaca", porque puedes escribir

  (setf (car somelist) whatever)

poner el auto de somelist.

Lo que realmente está sucediendo aquí es que "setf" es una macro. En el momento de la compilación, examina sus argumentos y ve que el primero tiene la forma (auto ALGO). Se dice a sí mismo "Oh, el programador está tratando de configurar el auto de algo. La función a usar para eso es 'rplaca'". Y silenciosamente reescribe el código en su lugar para:

  (rplaca somelist whatever)

55
setf es una buena ilustración del poder de las macros, gracias por incluirlo.
Joel

Me gusta lo más destacado ... porque la fuente de un programa Lisp no es una cadena; Es una lista. ! ¿Es la razón principal por la que LISP macro es superior a la mayoría de los demás debido a sus paréntesis?
Estudiante

@ Estudiante, supongo que sí: books.google.fr/… sugiere que tienes razón.
VonC

55

Las macros comunes de Lisp esencialmente extienden las "primitivas sintácticas" de su código.

Por ejemplo, en C, la construcción de cambio / caso solo funciona con tipos integrales y si desea usarla para flotantes o cadenas, le quedan instrucciones anidadas if y comparaciones explícitas. Tampoco hay forma de que pueda escribir una macro C para hacer el trabajo por usted.

Pero, dado que una macro lisp es (esencialmente) un programa lisp que toma fragmentos de código como entrada y devuelve código para reemplazar la "invocación" de la macro, puede extender su repertorio "primitivo" todo lo que desee, generalmente terminando con un programa más legible.

Para hacer lo mismo en C, tendría que escribir un preprocesador personalizado que comiera su fuente inicial (no bastante C) y escupe algo que un compilador de C puede entender. No es una forma incorrecta de hacerlo, pero no es necesariamente la más fácil.


41

Las macros Lisp le permiten decidir cuándo (si es que lo hace) alguna parte o expresión será evaluada. Para poner un ejemplo simple, piense en las C:

expr1 && expr2 && expr3 ...

Lo que esto dice es: evaluar expr1, y, en caso de ser cierto, evaluar expr2, etc.

Ahora intenta convertir esto &&en una función ... así es, no puedes. Llamando algo como:

and(expr1, expr2, expr3)

¡Evaluará los tres exprsantes de dar una respuesta, independientemente de si expr1fue falsa!

Con macros lisp puedes codificar algo como:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

ahora tiene un &&, que puede llamar como una función y no evaluará ningún formulario que le pase a menos que todos sean verdaderos.

Para ver cómo esto es útil, contraste:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

y:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

Otras cosas que puede hacer con las macros son crear nuevas palabras clave y / o mini idiomas (consulte la (loop ...)macro para ver un ejemplo), integrando otros idiomas en lisp, por ejemplo, podría escribir una macro que le permita decir algo como:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

Y eso ni siquiera es entrar en las macros de Reader .

Espero que esto ayude.


Creo que debería ser: "(aplique &&, @ exprs); ¡esto podría estar equivocado!"
Svante

1
@svante: en dos aspectos: primero, && es una macro, no una función; aplicar solo funciona en funciones. segundo, aplique tomar una lista de argumentos para pasar, por lo que desea uno de "(funcall fn, @ exprs)", "(aplicar fn (list, @ exprs)" o "(aplicar fn, @ exprs nil)", no "(aplicar fn, @ exprs)".
Aaron

(and ...evaluará las expresiones hasta que se evalúe como falso, tenga en cuenta que se producirán los efectos secundarios generados por la evaluación falsa, solo se omitirán las expresiones posteriores.
ocodo


10

Piense en lo que puede hacer en C o C ++ con macros y plantillas. Son herramientas muy útiles para administrar código repetitivo, pero están limitadas de manera bastante severa.

  • La sintaxis limitada de macro / plantilla restringe su uso. Por ejemplo, no puede escribir una plantilla que se expanda a algo que no sea una clase o una función. Las macros y las plantillas no pueden mantener fácilmente los datos internos.
  • La sintaxis compleja y muy irregular de C y C ++ hace que sea difícil escribir macros muy generales.

Las macros Lisp y Lisp resuelven estos problemas.

  • Las macros de Lisp están escritas en Lisp. Tienes todo el poder de Lisp para escribir la macro.
  • Lisp tiene una sintaxis muy regular.

Hable con cualquiera que domine C ++ y pregúnteles cuánto tiempo pasaron aprendiendo toda la falsificación de plantillas que necesitan para realizar la metaprogramación de plantillas. O todos los trucos locos en libros (excelentes) como Modern C ++ Design , que aún son difíciles de depurar y (en la práctica) no portátiles entre compiladores del mundo real a pesar de que el lenguaje se ha estandarizado durante una década. ¡Todo eso se desvanece si el lenguaje que usa para la metaprogramación es el mismo lenguaje que usa para la programación!


11
Bueno, para ser justos, el problema con la metaprogramación de plantillas C ++ no es que el lenguaje de metaprogramación sea diferente , sino que es horrible: no se diseñó tanto como se descubrió en lo que pretendía ser una funcionalidad de plantilla mucho más simple.
Brooks Moses el

@Brooks Claro. Las características emergentes no siempre son malas. Desafortunadamente, en un lenguaje impulsado por un comité de movimiento lento, es difícil solucionarlos cuando lo están. Es una pena que muchas de las nuevas y útiles funciones modernas de C ++ estén escritas en un lenguaje que pocos pueden leer, y existe una gran brecha entre el programador promedio y el "sumo sacerdote".
Matt Curtis

2
@downvoter: si hay algún problema con mi respuesta, deje un comentario para que todos podamos compartir el conocimiento.
Matt Curtis

10

Una macro lisp toma un fragmento de programa como entrada. Este fragmento de programa se representa como una estructura de datos que se puede manipular y transformar de la forma que desee. Al final, la macro genera otro fragmento de programa, y ​​este fragmento es lo que se ejecuta en tiempo de ejecución.

C # no tiene una función de macro, sin embargo, un equivalente sería si el compilador analizara el código en un árbol de CodeDOM y lo pasara a un método, que lo transformó en otro CodeDOM, que luego se compila en IL.

Esto podría usarse para implementar la sintaxis "azúcar" como la for eachdeclaración using-clause, linq -expressions, selectetc., como macros que se transforman en el código subyacente.

Si Java tuviera macros, podría implementar la sintaxis de Linq en Java, sin necesidad de que Sun cambie el idioma base.

Aquí hay un seudocódigo para ver cómo usingpodría verse una macro de estilo lisp en C # para implementar :

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

Ahora que en realidad hay un procesador de macro estilo Lisp para C #, señalaría que una macro usingse vería así ;)
Qwertie

9

Dado que las respuestas existentes brindan buenos ejemplos concretos que explican qué logran las macros y cómo, tal vez sería útil reunir algunas de las ideas sobre por qué la instalación macro es una ganancia significativa en relación con otros idiomas ; primero de estas respuestas, luego una excelente de otro lado:

... en C, tendría que escribir un preprocesador personalizado [que probablemente calificaría como un programa C suficientemente complicado ] ...

- Vatine

Hable con cualquiera que domine C ++ y pregúnteles cuánto tiempo pasaron aprendiendo toda la falsificación de plantillas que necesitan para realizar la metaprogramación de plantillas [que todavía no es tan poderosa].

- Matt Curtis

... en Java tienes que hackear tu camino con el tejido de código de bytes, aunque algunos frameworks como AspectJ te permiten hacer esto usando un enfoque diferente, es fundamentalmente un hack.

- Miguel Ping

DOLIST es similar al foreach de Perl o al de Python. Java agregó un tipo similar de construcción de bucle con el bucle "mejorado" para Java 1.5, como parte de JSR-201. Observe la diferencia que hacen las macros. Un programador Lisp que advierte un patrón común en su código puede escribir una macro para darse una abstracción a nivel fuente de ese patrón. Un programador de Java que advierte el mismo patrón tiene que convencer a Sun de que vale la pena agregar esta abstracción en particular al lenguaje. Entonces Sun tiene que publicar un JSR y convocar a un "grupo de expertos" de toda la industria para analizar todo. Ese proceso, según Sun, lleva un promedio de 18 meses. Después de eso, todos los escritores de compiladores deben actualizar sus compiladores para admitir la nueva característica. E incluso una vez que el compilador favorito del programador de Java admite la nueva versión de Java, probablemente "todavía" no pueden usar la nueva característica hasta que se les permita romper la compatibilidad de origen con versiones anteriores de Java. Por lo tanto, una molestia que los programadores de Common Lisp pueden resolver por sí mismos en cinco minutos afecta a los programadores de Java durante años.

- Peter Seibel, en "Practical Common Lisp"


8

No estoy seguro de poder agregar una idea a las publicaciones (excelentes) de todos, pero ...

Las macros de Lisp funcionan muy bien debido a la naturaleza de sintaxis de Lisp.

Lisp es un lenguaje extremadamente regular (piense que todo es una lista ); Las macros le permiten tratar los datos y el código de la misma manera (no se necesitan análisis de cadenas u otros hacks para modificar expresiones lisp). Combina estas dos características y tiene una forma muy limpia de modificar el código.

Editar: Lo que estaba tratando de decir es que Lisp es homoicónico , lo que significa que la estructura de datos para un programa lisp está escrita en el mismo lisp.

Entonces, terminas con una forma de crear tu propio generador de código en la parte superior del lenguaje usando el lenguaje en sí con toda su potencia (por ejemplo, en Java tienes que hackear tu camino con el tejido de código de bytes, aunque algunos marcos como AspectJ te permiten hacer esto usando un enfoque diferente, es fundamentalmente un truco).

En la práctica, con las macros terminas construyendo tu propio mini-idioma además de lisp, sin la necesidad de aprender idiomas o herramientas adicionales, y con el uso de todo el poder del lenguaje en sí.


1
Es un comentario perspicaz, sin embargo, esta idea de que "todo es una lista" puede asustar a los recién llegados. Para comprender una lista, debe comprender los contras, los automóviles, los CD, las celdas. Más precisamente, Lisp está hecho S-expressions, no listas.
ribamar

6

Las macros de Lisp representan un patrón que ocurre en casi cualquier proyecto de programación considerable. Eventualmente, en un programa grande, tiene una cierta sección de código donde se da cuenta de que sería más simple y menos propenso a errores escribir un programa que genere código fuente como texto que luego puede pegar.

En Python los objetos tienen dos métodos __repr__y __str__. __str__es simplemente la representación humana legible. __repr__devuelve una representación que es código Python válido, es decir, algo que se puede ingresar en el intérprete como Python válido. De esta manera, puede crear pequeños fragmentos de Python que generen código válido que se pueda pegar en su fuente real.

En Lisp todo este proceso ha sido formalizado por el sistema macro. Claro que le permite crear extensiones a la sintaxis y hacer todo tipo de cosas elegantes, pero su utilidad real se resume en lo anterior. Por supuesto, ayuda que el sistema macro Lisp le permita manipular estos "fragmentos" con toda la potencia de todo el lenguaje.


1
Su primer párrafo es muy claro para un extraño de Lisp, lo cual es importante.
Comodín

5

En resumen, las macros son transformaciones de código. Permiten introducir muchas nuevas construcciones de sintaxis. Por ejemplo, considere LINQ en C #. En lisp, hay extensiones de lenguaje similares implementadas por macros (por ejemplo, construcción de bucle incorporada, iteración). Las macros disminuyen significativamente la duplicación de código. Las macros permiten incrustar «pequeños lenguajes» (p. Ej., Donde en c # / java se usaría xml para configurar, en lisp se puede lograr lo mismo con las macros). Las macros pueden ocultar dificultades de uso de bibliotecas.

Por ejemplo, en lisp puedes escribir

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

y esto oculta todas las cosas de la base de datos (transacciones, cierre de conexión adecuado, obtención de datos, etc.) mientras que en C # esto requiere crear SqlConnections, SqlCommands, agregar SqlParameters a SqlCommands, hacer un bucle en SqlDataReaders, cerrarlos correctamente.


3

Si bien todo lo anterior explica qué son las macros e incluso tienen ejemplos geniales, creo que la diferencia clave entre una macro y una función normal es que LISP evalúa todos los parámetros primero antes de llamar a la función. Con una macro es al revés, LISP pasa los parámetros sin evaluar a la macro. Por ejemplo, si pasa (+ 1 2) a una función, la función recibirá el valor 3. Si pasa esto a una macro, recibirá una Lista (+ 1 2). Esto se puede usar para hacer todo tipo de cosas increíblemente útiles.

  • Agregar una nueva estructura de control, por ejemplo, un bucle o la deconstrucción de una lista
  • Mida el tiempo que lleva ejecutar una función. Con una función, el parámetro se evaluaría antes de pasar el control a la función. Con la macro, puede empalmar su código entre el inicio y la detención de su cronómetro. El siguiente tiene exactamente el mismo código en una macro y una función y el resultado es muy diferente. Nota: Este es un ejemplo artificial y la implementación se eligió de modo que sea idéntica para resaltar mejor la diferencia.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0

Por cierto, Scala ha agregado macros al lenguaje. Si bien carecen de la belleza de las macros de Lisp porque el lenguaje no es homicónico, definitivamente vale la pena analizarlo y los árboles de sintaxis abstracta que proporcionan podrían ser más fáciles de usar al final. Es demasiado pronto para decir qué sistema macro prefiero.
Joerg Schmuecker

2
"LISP pasa los parámetros sin evaluar a la macro", finalmente una respuesta que lo dice claramente. pero olvidó la segunda mitad de eso: y el resultado de una macro es un código transformado que será evaluado completo por el sistema en lugar del original como si estuviera allí en primer lugar (a menos que sea nuevamente una llamada a una macro, que también se transformará, por esa macro esta vez).
Will Ness

0

Obtuve esto del libro de cocina común de lisp, pero creo que explica por qué las macros de lisp son buenas de una manera agradable.

"Una macro es una pieza ordinaria de código Lisp que opera en otra pieza de código Lisp putativo, traduciéndolo a (una versión más cercana a) Lisp ejecutable. Eso puede sonar un poco complicado, así que vamos a dar un ejemplo simple. Supongamos que quiere un versión de setq que establece dos variables en el mismo valor. Entonces, si escribe

(setq2 x y (+ z 3))

cuando z=8tanto x como y se establecen en 11. (No se me ocurre ningún uso para esto, pero es solo un ejemplo).

Debería ser obvio que no podemos definir setq2 como una función. Si x=50y y=-5, esta función recibiría los valores 50, -5 y 11; no tendría conocimiento de qué variables se suponía que debían establecerse. Lo que realmente queremos decir es que, cuando (el sistema Lisp) ve (setq2 v1 v2 e), lo trata como equivalente a (progn (setq v1 e) (setq v2 e)). En realidad, esto no está del todo bien, pero lo hará por ahora. Una macro nos permite hacer precisamente esto, al especificar un programa para transformar el patrón de entrada (setq2 v1 v2 e)"en el patrón de salida (progn ...)".

Si pensabas que esto era bueno, puedes seguir leyendo aquí: http://cl-cookbook.sourceforge.net/macros.html


1
Es posible definir setq2como una función si xy yse pasan por referencia. Sin embargo, no sé si es posible en CL. Entonces, para alguien que no conoce a Lisps o CL en particular, este no es un ejemplo muy ilustrativo de la OMI
neoascético el

@neoascetic El argumento CL pasa solo por valor (es por eso que necesita macros en primer lugar). Sin embargo, algunos valores son punteros (como las listas).
Will Ness

-5

En python tienes decoradores, básicamente tienes una función que toma otra función como entrada. Puede hacer lo que quiera: llamar a la función, hacer otra cosa, envolver la llamada a la función en una versión de adquisición de recursos, etc., pero no puede mirar dentro de esa función. Digamos que queríamos hacerlo más poderoso, digamos que su decorador recibió el código de la función como una lista, entonces no solo podría ejecutar la función tal como está sino que ahora puede ejecutar partes de ella, reordenar líneas de la función, etc.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.