macros - utiliza - programacion funcional preguntas
¿Buenos ejemplos del uso de macros de Clojure que demuestran las ventajas del lenguaje en general? (6)
Las macros me parecen bastante útiles para definir nuevas características de lenguaje. En la mayoría de los idiomas, tendrá que esperar una nueva versión del idioma para obtener una nueva sintaxis. En Lisp, puede ampliar el lenguaje principal con macros y agregar las funciones usted mismo.
Por ejemplo, Clojure no tiene un estilo C imperativo for(i=0 ;i<10; i++)
bucle for(i=0 ;i<10; i++)
pero puede agregar fácilmente uno con una macro:
(defmacro for-loop [[sym init check change :as params] & steps]
(cond
(not (vector? params))
(throw (Error. "Binding form must be a vector for for-loop"))
(not= 4 (count params))
(throw (Error. "Binding form must have exactly 4 arguments in for-loop"))
:default
`(loop [~sym ~init value# nil]
(if ~check
(let [new-value# (do ~@steps)]
(recur ~change new-value#))
value#))))
Uso de la siguiente manera:
(for-loop [i 0, (< i 10), (inc i)]
(println i))
Si es una buena idea agregar un bucle imperativo a un lenguaje funcional es un debate que probablemente deberíamos evitar aquí :-)
Estoy pensando en aprender Clojure, pero provenir del mundo de lenguajes imperativos basado en sintaxis c (java, php, c #) que va a ser un desafío, por lo que uno naturalmente se pregunta, ¿realmente vale la pena? Y aunque esa pregunta puede ser muy subjetiva y difícil de manejar, hay un rasgo específico de Clojure (y, más en general, las risas) sobre el que sigo leyendo, que se supone que es el lenguaje más flexible que nunca: las macros.
¿Tiene algún buen ejemplo de uso de macros en Clojure, para fines que en otros lenguajes principales (considere cualquiera de C ++, PHP, Perl, Python, Groovy / Java, C #, JavaScript) requeriría soluciones mucho menos elegantes / mucho innecesarias? abstracción / hacks / etc.
Las macros son parte de Clojure pero, en mi humilde opinión, no creo que sean por eso que debes o no debes aprender Clojure La inmutabilidad de los datos, las buenas construcciones para manejar el estado concurrente y el hecho de que es un lenguaje JVM y puede aprovechar el código Java son tres razones. Si no puede encontrar otra razón para aprender Clojure, considere el hecho de que un lenguaje de programación funcional probablemente debería afectar positivamente la forma en que aborda los problemas en cualquier idioma.
Para observar las macros, le sugiero que comience con las macros de subprocesos de Clojure: thread-first y thread-last ->
y ->>
respectivamente; Visite esta página y muchos de los diversos blogs que tratan sobre Clojure .
Buena suerte y diviertete.
Parte del código real de un proyecto en el que estaba trabajando: quería anidar bucles for-esque usando ints sin caja.
(defmacro dofor
[[i begin end step & rest] & body]
(when step
`(let [end# (long ~end)
step# (long ~step)
comp# (if (< step# 0)
>
<)]
(loop [~i ~begin]
(when (comp# ~i end#)
~@(if rest
`((dofor ~rest ~@body))
body)
(recur (unchecked-add ~i step#)))))))
Utilizado como
(dofor [i 2 6 2
j i 6 1]
(println i j))
Que imprime
2 2
2 3
2 4
2 5
4 4
4 5
Se compila a algo muy cercano al loop
/ recur
bruto que originalmente estaba escribiendo a mano, por lo que básicamente no hay una penalización de rendimiento en tiempo de ejecución, a diferencia del equivalente
(doseq [i (range 2 6 2)
j (range i 6 1)]
(println i j))
Creo que el código resultante se compara favorablemente con el equivalente de Java:
for (int i = 2; i < 6; i+=2) {
for (int j = i; j < 6; j++) {
System.out.println(i+" "+j);
}
}
También hay un caso de uso más esotérico para las macros que a veces uso: escribir código conciso y legible que también esté totalmente optimizado. Aquí hay un ejemplo trivial:
(defmacro str* [& ss] (apply str (map eval ss)))
Lo que esto hace es concatenar cadenas en tiempo de compilación (tienen que ser constantes de tiempo de compilación, por supuesto). La función normal de concatenación de cadenas en Clojure es str
por lo que en un código de bucle cerrado tengo una cadena larga que me gustaría dividir en varios literales de cadenas, solo agrego la estrella a str
y cambio la concatenación de tiempo de ejecución a compilación. Uso:
(str* "I want to write some very lenghty string, most often it will be a complex"
" SQL query. I''d hate if it meant allocating the string all over every time"
" this is executed.")
Otro ejemplo, menos trivial:
(defmacro jprint [& xs] `(doto *out* ~@(for [x xs] `(.append ~x))))
El &
significa que acepta un número variable de argumentos (varargs, función variadic). En Clojure, una llamada de función variable hace uso de una colección asignada al montón para transferir los argumentos (como en Java, que utiliza una matriz). Esto no es muy óptimo, pero si uso una macro como la anterior, entonces no hay una llamada de función. Lo uso así:
(jprint /" (json-escape item) /")
Se compila en tres invocaciones de PrintWriter.append
(básicamente un bucle desenrollado).
Finalmente, me gustaría mostrarles algo aún más radicalmente diferente. Puede usar una macro para ayudarlo a definir una clase de funciones similares, eliminando grandes cantidades de repetitivo. Tomemos este ejemplo familiar: en una biblioteca de cliente HTTP queremos una función separada para cada uno de los métodos HTTP. Cada definición de función es bastante compleja ya que tiene cuatro firmas sobrecargadas. Además, cada función implica una clase de solicitud diferente de la biblioteca HttpClient de Apache, pero todo lo demás es exactamente igual para todos los métodos HTTP. Mira cuánto código necesito para manejar esto.
(defmacro- def-http-method [name]
`(defn ~name
([~''url ~''headers ~''opts ~''body]
(handle (~(symbol (str "Http" (s/capitalize name) ".")) ~''url) ~''headers ~''opts ~''body))
([~''url ~''headers ~''opts] (~name ~''url ~''headers ~''opts nil))
([~''url ~''headers] (~name ~''url ~''headers nil nil))
([~''url] (~name ~''url nil nil nil))))
(doseq [m [''GET ''POST ''PUT ''DELETE ''OPTIONS ''HEAD]]
(eval `(def-http-method ~m)))
Un ejemplo simple de una macro útil que es bastante difícil de recrear sin macros es doto
. Evalúa su primer argumento y luego evalúa los siguientes formularios, insertando el resultado de la evaluación como su primer argumento. Esto puede no parecer mucho, pero ...
Con doto
esto:
(let [tmpObject (produceObject)]
(do
(.setBackground tmpObject GREEN)
(.setThis tmpObject foo)
(.setThat tmpObject bar)
(.outputTo tmpObject objectSink)))
Se convierte en eso:
(doto (produceObject)
(.setBackground GREEN)
(.setThis foo)
(.setThat bar)
(.outputTo objectSink))
Lo importante es que el doto
no es magia: puede (re) construirlo usted mismo usando las características estándar del lenguaje.
hay muchas macros en la base de clojure en las que no piensas ... Lo que es un signo de una buena macro, te permiten extender el lenguaje de una manera que hace la vida más fácil. Sin macros la vida sería mucho menos emocionante. por ejemplo, si no tuviéramos
(with-out-str (somebody else''s code that prints to screen))
entonces necesitarías modificar su código de manera que no tengas acceso.
otro gran ejemplo es
(with-open-file [fh (open-a-file-code ...)]
(do (stuff assured that the file won''t leak)))
Todo el patrón de macros with-something-do
se ha agregado realmente al sistema ecológico clojure.
el otro lado de la proverbial macro moneda es que paso prácticamente todo mi (actual) tiempo profesional de Clojure utilizando una biblioteca muy macroscópica y, por lo tanto, dedico mucho tiempo al hecho de que las macros no se componen bien y no lo son. primera clase Los autores de esta biblioteca harán todo lo posible en la próxima versión para hacer que todas las funciones estén disponibles sin tener que pasar por las macros para permitir que las personas como yo las utilicen en funciones de orden superior como map
y reduce
.
Las macros mejoran el mundo cuando hacen la vida más fácil. Pueden tener el efecto contrario cuando son la única interfaz para una biblioteca. por favor no uses macros como interfaces
En general, es difícil obtener la forma correcta de sus datos. Si, como autor de una biblioteca, tiene los datos bien estructurados de acuerdo con la forma en que imagina que se utilizará su biblioteca, es muy posible que haya una manera de reestructurar las cosas para permitir que los usuarios empleen su biblioteca de formas nuevas e inimaginables. En este caso, la estructura de la fantástica biblioteca en cuestión era bastante buena, permitía cosas que los autores no habían querido. Desafortunadamente, una gran biblioteca estaba restringida porque su interfaz era un conjunto de macros, no un conjunto de funciones. La biblioteca era mejor que sus macros, así que la retuvieron . Esto no quiere decir que las macros sean culpables de ninguna manera, solo que la programación es difícil y son otra herramienta que puede tener muchos efectos y todas las piezas deben usarse juntas para funcionar bien.