clojure lisp

Cómo leer mentalmente el código Lisp/Clojure



(3)

Creo que concat es un mal ejemplo para tratar de entender. Es una función central y es más de bajo nivel que el código que normalmente se escribiría usted mismo, porque se esfuerza por ser eficiente.

Otra cosa a tener en cuenta es que el código de Clojure es extremadamente denso en comparación con el código de Java. Un pequeño código de Clojure hace mucho trabajo. El mismo código en Java no sería 23 líneas. Es probable que haya múltiples clases e interfaces, muchos métodos, muchas variables de descarte temporal local y construcciones torcidas de bucle y en general todo tipo de texto repetitivo.

Algunos consejos generales, aunque ...

  1. Intenta ignorar a los parens la mayor parte del tiempo. Use la sangría en su lugar (como sugiere Nathan Sanders). p.ej

    (if s (if (chunked-seq? s) (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) (cons (first s) (concat (rest s) y))) y))))

    Cuando miro eso, mi cerebro ve:

    if foo then if bar then baz else quux else blarf

  2. Si coloca el cursor en una carpeta y su editor de texto no tiene sintaxis, resalte la que coincida, le sugiero que busque un nuevo editor.

  3. Algunas veces es útil leer el código al revés. El código de Clojure tiende a estar profundamente anidado.

    (let [xs (range 10)] (reverse (map #(/ % 17) (filter (complement even?) xs))))

    Malo: "Así que comenzamos con los números del 1 al 10. Entonces estamos invirtiendo el orden del mapeo del filtrado del complemento de la espera. Olvidé de lo que estoy hablando".

    Bueno: "OK, entonces estamos tomando algo xs . (complement even?) Significa lo opuesto a par, tan" impar ". Así que estamos filtrando una colección, así que solo quedan los números impares. Luego los dividimos todos por 17. Entonces estamos invirtiendo el orden de ellos. Y los xs en cuestión son de 1 a 10, gotcha ".

    Algunas veces ayuda hacer esto explícitamente. Tome los resultados intermedios, tírelos y déles un nombre para que lo entienda. El REPL está hecho para jugar de esta manera. Ejecute los resultados intermedios y vea lo que le da cada paso.

    (let [xs (range 10) odd? (complement even?) odd-xs (filter odd? xs) odd-xs-over-17 (map #(/ % 17) odd-xs) reversed-xs (reverse odd-xs-over-17)] reversed-xs)

    Pronto podrás hacer este tipo de cosas mentalmente sin esfuerzo.

  4. Haga un uso liberal de (doc) . La utilidad de tener documentación disponible en el REPL no puede exagerarse. Si usa clojure.contrib.repl-utils y tiene sus archivos .clj en classpath, puede hacer (source some-function) y ver todo el código fuente para ello. Puede hacer (show some-java-class) y ver una descripción de todos los métodos en él. Y así.

Poder leer algo rápidamente solo viene con la experiencia. Lisp no es más difícil de leer que cualquier otro idioma. Sucede que la mayoría de los lenguajes se ven como C, y la mayoría de los programadores pasan la mayor parte del tiempo leyendo eso, así que parece que la sintaxis C es más fácil de leer. Práctica práctica práctica.

¡Muchas gracias por todas las hermosas respuestas! No puedo marcar solo uno como correcto

Nota: Ya es una wiki

Soy nuevo en la programación funcional y aunque puedo leer funciones simples en la programación funcional, por ejemplo, al calcular el factorial de un número, me resulta difícil leer las funciones grandes. Parte de la razón es que creo debido a mi incapacidad para descifrar los bloques de código más pequeños dentro de una definición de función y también en parte porque me resulta difícil hacer coincidir ( ) en el código.

Sería genial si alguien me guiara leyendo algún código y me diera algunos consejos sobre cómo descifrar rápidamente algún código.

Nota: Puedo entender este código si lo miro por 10 minutos, pero dudo que este mismo código haya sido escrito en Java, me tomaría 10 minutos. Por lo tanto, creo que para sentirme cómodo con el código de estilo Lisp, debo hacerlo más rápido

Nota: Sé que esta es una pregunta subjetiva. Y no estoy buscando una respuesta correcta que sea convincente aquí. Solo comentarios sobre cómo leer este código, serían bienvenidos y de gran ayuda

(defn concat ([] (lazy-seq nil)) ([x] (lazy-seq x)) ([x y] (lazy-seq (let [s (seq x)] (if s (if (chunked-seq? s) (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) (cons (first s) (concat (rest s) y))) y)))) ([x y & zs] (let [cat (fn cat [xys zs] (lazy-seq (let [xys (seq xys)] (if xys (if (chunked-seq? xys) (chunk-cons (chunk-first xys) (cat (chunk-rest xys) zs)) (cons (first xys) (cat (rest xys) zs))) (when zs (cat (first zs) (next zs)))))))] (cat (concat x y) zs))))


El código Lisp, en particular, es aún más difícil de leer que otros lenguajes funcionales debido a la sintaxis regular. Wojciech da una buena respuesta para mejorar su comprensión semántica. Aquí hay algo de ayuda sobre la sintaxis.

Primero, al leer el código, no se preocupe por paréntesis. Preocuparse por la sangría. La regla general es que las cosas en el mismo nivel de sangrado están relacionadas. Asi que:

(if (chunked-seq? s) (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) (cons (first s) (concat (rest s) y)))

En segundo lugar, si no puede caber todo en una línea, sangría la siguiente línea una pequeña cantidad. Esto es casi siempre dos espacios:

(defn concat ([] (lazy-seq nil)) ; these two fit ([x] (lazy-seq x)) ; so no wrapping ([x y] ; but here (lazy-seq ; (lazy-seq indents two spaces (let [s (seq x)] ; as does (let [s (seq x)]

Tercero, si múltiples argumentos para una función no pueden caber en una sola línea, alinee los argumentos segundo, tercero, etc. debajo del primer paréntesis inicial. Muchas macros tienen una regla similar con variaciones para permitir que las partes importantes aparezcan primero.

; fits on one line (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) ; has to wrap: line up (cat ...) underneath first ( of (chunk-first xys) (chunk-cons (chunk-first xys) (cat (chunk-rest xys) zs)) ; if you write a C-for macro, put the first three arguments on one line ; then the rest indented two spaces (c-for (i 0) (< i 100) (add1 i) (side-effects!) (side-effects!) (get-your (side-effects!) here))

Estas reglas te ayudan a encontrar bloques dentro del código: si ves

(chunk-cons (chunk-first s)

¡No cuente paréntesis! Verifique la siguiente línea:

(chunk-cons (chunk-first s) (concat (chunk-rest s) y))

Sabes que la primera línea no es una expresión completa porque la siguiente línea está indentada debajo de ella.

Si ves el defn concat desde arriba, sabes que tienes tres bloques, porque hay tres cosas en el mismo nivel. Pero todo lo que está debajo de la tercera línea está indentado debajo de él, por lo que el resto pertenece a ese tercer bloque.

Aquí hay una guía de estilo para Scheme . No conozco Clojure, pero la mayoría de las reglas deberían ser las mismas ya que ninguno de los otros Lisps varía mucho.


Primero recuerde que el programa funcional consiste en expresiones, no declaraciones. Por ejemplo, form (if condition expr1 expr2) toma su 1st arg como condición para probar el boolean falue, lo evalúa, y si se evalúa como verdadero, evalúa y devuelve expr1, de lo contrario evalúa y devuelve expr2. Cuando cada formulario devuelve una expresión, algunas de las construcciones de sintaxis habituales, como ENTONCES o ELA, pueden desaparecer. Tenga en cuenta que aquí if también se evalúa a una expresión.

Ahora sobre la evaluación: en Clojure (y otros Lisps) la mayoría de las formas con las que te encuentras son llamadas a funciones de la forma (f a1 a2 ...) , donde todos los argumentos a f se evalúan antes de la llamada a la función real; pero las formas también pueden ser macros o formas especiales que no evalúan algunos (o todos) sus argumentos. En caso de duda, consulte la documentación (doc f) o simplemente marque REPL:

user=> apply
#<core$apply__3243 clojure.core$apply__3243@19bb5c09>
user=> apply
#<core$apply__3243 clojure.core$apply__3243@19bb5c09>
a function
user=> doseq
java.lang.Exception: Can''t take value of a macro: #''clojure.core/doseq
user=> doseq
java.lang.Exception: Can''t take value of a macro: #''clojure.core/doseq
a macro.

Estas dos reglas:

  • tenemos expresiones, no declaraciones
  • la evaluación de un subformulario puede ocurrir o no, dependiendo de cómo se comporte la forma externa

debería facilitar su elaboración de los programas Lisp, esp. si tienen una buena sangría como el ejemplo que diste.

Espero que esto ayude.