2012-04-24 20 views
9

que comenzó a aprender Clojure recientemente. En general, parece interesante, pero no puedo acostumbrarme a algunos inconvenientes sintácticos (en comparación con la experiencia anterior de Ruby/C#).transición de infija a la notación de prefijo

notación de prefijo para las expresiones anidadas. En Ruby me acostumbro a escribir expresiones complejas con encadenamiento/conexionado de izquierda a derecha: some_object.map { some_expression }.select { another_expression }. Es realmente conveniente a medida que pasa del valor de entrada al resultado paso a paso, puede enfocarse en una sola transformación y no es necesario mover el cursor mientras escribe. Contrariamente a eso cuando escribo expresiones anidadas en Clojure, escribo el código de expresión interna a externo y tengo que mover el cursor constantemente. Se ralentiza y distrae. Sé acerca de las macros -> y ->> pero noté que no es una idiomática. ¿Tuviste el mismo problema cuando comenzaste a codificar en Clojure/Haskell, etc.? ¿Cómo lo resolvió?

+0

Creo que es solo idiomático porque es a lo que estás acostumbrado. La notación de prefijo es mucho más consistente que infijo, por lo que puede ser más fácil de leer una vez que te acostumbras. –

+0

Mike, considere que necesita escribir la expresión '(filter # (expr1) (map # (expr2) expr3))'. ¿Comenzarías a escribir desde expr3 interno o desde filtro? – Alexey

+3

Normalmente empiezo desde afuera y me abro camino. Así que pienso en lo que quiero y simplemente retrocedo. –

Respuesta

3

Yo de hecho veo el mismo obstáculo cuando empecé con un ceceo y era muy molesto hasta que vi las formas en que hace el código más simple y más clara, una vez que entendí lo positivo, el enfado se desvaneció

initial + scale + offset 

convirtió

(+ initial scale offset) 

y luego tratar (+) notación de prefijo permite funciones para especificar valores de su propia identidad

user> (*) 
1 
user> (+) 
0 

Hay muchos más ejemplos y mi punto no es defender la notación de prefijo. Solo espero transmitir que la curva de aprendizaje se aplana (emocionalmente) a medida que los lados positivos se hacen evidentes.

, por supuesto, al iniciar escribiendo macros, la notación de prefijo se convierte en una necesidad de tener en lugar de una conveniencia.


para resolver la segunda parte de su pregunta, el hilo primero y último hilo de macros son en cualquier momento idiomática que hacen que el código sea más claro :) que se utilizan con más frecuencia en las funciones de llamadas que la aritmética pura, aunque nadie lo culpa que para usarlos cuando hacen que la ecuación sea más agradable.


ps: (.. object object2 object3) ->object().object2().object3();

(doto my-object 
    (setX 4) 
    (sety 5)` 
+0

Arthur, ¿crees que la notación de prefijo es tan conveniente para escribir el código como la invocación infija de '.methods' con dot, o es realmente menos conveniente pero tiene otras ventajas? – Alexey

+1

una vez que encontré la macro '..' encontré la notación inflix en jave muy engorrosa y difícil de leer y escribir. (esta es solo mi humilde opinión) –

+1

@ArthurUlfeldt, ¿pueden dar más detalles sobre "por supuesto, cuando comienzas a escribir macros, entonces la notación inflix se convierte en una herramienta imprescindible en lugar de en una conveniencia"? ¿O quisiste decir el prefijo aquí? – ivant

4

Que yo sepa -> y ->> son idiomática en Clojure. Los uso todo el tiempo, y en mi opinión, generalmente conducen a un código mucho más legible.

Éstos son algunos ejemplos de estas macros se utilizan en proyectos populares de todo el "ecosistema" Clojure:

Demostración por ejemplo :)

9

Sentí lo mismo sobre Lisps inicialmente así que siento tu dolor :-)

Sin embargo, la buena noticia es que con un poco de tiempo y uso regular probablemente comiences a gustar la notación de prefijo. De hecho, con la excepción de las expresiones matemáticas, ahora prefiero infijar el estilo.

razones para como notación de prefijo:

  • Coherencia con funciones - la mayoría de los idiomas utilizan una mezcla de infija (operadores matemáticos) y la notación de prefijo (llamada funcional). En Lisps todo es consecuente y tiene cierta elegancia si consideramos que los operadores matemáticos son funciones
  • Macros - se vuelven mucho más lógicos si la llamada de función está siempre en la primera posición.
  • Varargs - es bueno poder tener una cantidad variable de parámetros para casi todos sus operadores. (+ 1 2 3 4 5) es mejor que en mi humilde opinión 1 + 2 + 3 + 4 + 5

Un truco es, entonces, utilizar -> y ->> librerally cuando tiene sentido lógico para estructurar su código de esta manera. Esto es típicamente útil cuando se trata de operaciones posteriores en objetos o colecciones, p.

(->> 
    "Hello World" 
    distinct 
    sort 
    (take 3)) 

==> (\space \H \W) 

El truco final me pareció muy útil cuando se trabaja en el estilo de prefijo es hacer un buen uso de la sangría al construir expresiones más complejas. Si aplica sangría correctamente, a continuación, usted encontrará que la notación de prefijo es en realidad bastante clara para que diga:

(defn add-foobars [x y] 
    (+ 
    (bar x y) 
    (foo y) 
    (foo x))) 
+0

Inconviniencia con '->' y '- >>' es que tienes dos de ellos. Algunas funciones ('cons',' map', 'apply') toman el objeto compuesto como último argumento, y otras (' conj', 'update-in') como primeras y no se pueden encadenar a ellas con solo' -> 'o '- >>'. PDEn los lenguajes POO puros como Ruby y Smalltalk la notación infija es completamente consistente: '1. + (2), [1] .zip ([2])' y el objeto compuesto siempre es el argumento antes del punto. – Alexey

+3

@Alexey: hay una razón para '->' y '- >>' y el orden de los argumentos diferentes. Se debe a la distinción entre la abstracción seq y las colecciones. Creo que esto confunde porque una colección a menudo se trata como una seq. A menudo me remito a la explicación de Rich Hickey: http://groups.google.com/group/clojure/msg/a8866d34b601ff43 –

+2

Además, si realmente lo desea, la biblioteca swiss-arrows proporciona la macro de la varilla de diamante: http: // stackoverflow .com/q/10068398/148578 –

3

Si usted tiene una cadena larga de expresión, utilice let. Las expresiones de larga huida o las expresiones profundamente anidadas no son especialmente legibles en ningún idioma. Esto es malo:

(do-something (map :id (filter #(> (:age %) 19) (fetch-data :people)))) 

Esto es marginalmente mejor:

(do-something (map :id 
        (filter #(> (:age %) 19) 
          (fetch-data :people)))) 

Pero esto también es malo:

fetch_data(:people).select{|x| x.age > 19}.map{|x| x.id}.do_something 

Si estamos leyendo esto, ¿qué necesitamos saber? Estamos llamando al do_something sobre algunos atributos de algún subconjunto de people. Este código es difícil de leer porque hay tanta distancia entre el primero y el último, que nos olvidamos de lo que estamos mirando en el momento en que viajamos entre ellos.

En el caso de Ruby, do_something (o lo que sea que está produciendo nuestro resultado final) se pierde camino al final de la línea, por lo que es difícil decir lo que estamos haciendo a nuestro people. En el caso de Clojure, es inmediatamente obvio que do-something es lo que estamos haciendo, pero es difícil decir a qué lo estamos haciendo sin leer todo el asunto desde adentro.

Cualquier código más complejo que este simple ejemplo va a ser muy doloroso. Si todo tu código se ve así, tu cuello se cansará al explorar todas estas líneas de espagueti.

prefiero algo como esto:

(let [people (fetch-data :people) 
     adults (filter #(> (:age %) 19) people) 
     ids (map :id adults)] 
    (do-something ids)) 

Ahora es obvio: Comienzo con people, que joder, y luego me do-something a ellos.

Y puede salirse con la suya:

fetch_data(:people).select{|x| 
    x.age > 19 
}.map{|x| 
    x.id 
}.do_something 

Pero probablemente preferiría hacer esto, por lo menos:

adults = fetch_data(:people).select{|x| x.age > 19} 
do_something(adults.map{|x| x.id}) 

Tampoco es raro utilizar let incluso cuando sus expresiones intermedias no tienen buenos nombres. (Este estilo se utiliza de vez en cuando en el propio código fuente de Clojure, por ejemplo, el código fuente de defmacro)

(let [x (complex-expr-1 x) 
     x (complex-expr-2 x) 
     x (complex-expr-3 x) 
     ... 
     x (complex-expr-n x)] 
    (do-something x)) 

Esto puede ser de gran ayuda en la depuración, porque se puede inspeccionar las cosas en cualquier momento haciendo:

(let [x (complex-expr-1 x) 
     x (complex-expr-2 x) 
     _ (prn x) 
     x (complex-expr-3 x) 
     ... 
     x (complex-expr-n x)] 
    (do-something x)) 
Cuestiones relacionadas