15

OK, no hay trampas ahora.¿Es funcional Clojure o imperativo Groovy más legible?

No, realmente, tómese un minuto o dos y pruebe esto.

¿Qué significa "posiciones"?

Editar: simplificado según la sugerencia de cgrand.

(defn redux [[current next] flag] [(if flag current next) (inc next)]) 

(defn positions [coll] 
    (map first (reductions redux [1 2] (map = coll (rest coll))))) 

Ahora, ¿qué tal esta versión?

def positions(coll) { 
    def (current, next) = [1, 1] 
    def previous = coll[0] 
    coll.collect { 
    current = (it == previous) ? current : next 
    next++ 
    previous = it 
    current 
    } 
} 

estoy aprendiendo Clojure y yo soy amante de ella, porque siempre he disfrutado de la programación funcional. Me llevó más tiempo encontrar la solución Clojure, pero disfruté al tener que pensar en de una solución elegante. La solución Groovy está bien, pero estoy en el punto donde encuentro este tipo de programación imperativa aburrida y mecánica. Después de 12 años de Java, me siento atrapado y la programación funcional con Clojure es el impulso que necesitaba.

Derecho, ir al grano. Bueno, tengo que ser honesto y decir que me pregunto si entenderé el código de Clojure cuando regrese a él meses después. Claro que podría comentar todo lo diablos, pero no necesito comentar mi código Java para entenderlo.

Así que mi pregunta es: ¿se trata de acostumbrarse a los patrones de programación funcional? ¿Los gurús de la programación funcional están leyendo este código y consideran que es muy fácil de entender? ¿Qué versión hizo usted más fácil de entender?

Editar: lo que este código hace es calcular las posiciones de los jugadores de acuerdo a sus puntos, al mismo tiempo hacer un seguimiento de los que están atados. Por ejemplo:


Pos Points 
1. 36 
1. 36 
1. 36 
4. 34 
5. 32 
5. 32 
5. 32 
8. 30 

Respuesta

23

No creo que haya algo de legibilidad intrínseca. Hay a lo que estás acostumbrado y a lo que no estás acostumbrado. Pude leer ambas versiones de tu código OK. De hecho, pude leer su versión de Groovy más fácilmente, aunque no conozco a Groovy, porque también pasé una década mirando C y Java y solo un año mirando a Clojure. Eso no dice nada sobre los idiomas, solo dice algo sobre mí.

De manera similar, puedo leer inglés más fácilmente que el español, pero eso tampoco dice nada sobre la legibilidad intrínseca de esos idiomas. (El español es probablemente el idioma "más legible" de los dos en términos de simplicidad y consistencia, pero todavía no puedo leerlo). Estoy aprendiendo japonés en este momento y estoy pasando un mal rato, pero los hablantes nativos de japonés dicen lo mismo sobre el inglés.

Si pasaste la mayor parte de tu vida leyendo Java, por supuesto, las cosas que se parecen a Java serán más fáciles de leer que las que no. Hasta que haya pasado tanto tiempo mirando los lenguajes Lispy como viendo lenguajes tipo C, esto probablemente seguirá siendo cierto.

Para entender un idioma, entre otras cosas que usted tiene que estar familiarizado con:

  • sintaxis ([vector] vs (list), hyphens-in-names)
  • vocabulario (lo que quiere decir reductions Cómo/donde puede buscar? ¿arriba?)
  • reglas de evaluación (no el tratamiento funciona como funcionan los objetos? Es un error en la mayoría de los idiomas.)
  • modismos, como (map first (some set of reductions with extra accumulated values))

Todo esto llevará tiempo y práctica y la repetición para aprender e interiorizar. Pero si pasas los próximos 6 meses leyendo y escribiendo montones de Clojure, no solo serás capaz de entender ese código de Clojure dentro de 6 meses, probablemente lo entiendas mejor que ahora, y tal vez incluso puedas simplificar eso. ¿Qué tal esto:

(use 'clojure.contrib.seq-utils)          ;;' 
(defn positions [coll] 
    (mapcat #(repeat (count %) (inc (ffirst %))) 
      (partition-by second (indexed coll)))) 

mirar el código Clojure que escribí hace un año, estoy horrorizado por lo malo que es, pero puedo leerlo bien. (No digo que tu código Clojure es horrible, no tuve problemas para leerlo, y no soy un gurú.)

+0

Excelente respuesta. Me encanta tu versión. Debo admitir que me tomó un tiempo conseguirlo. ¡Es bastante inteligente! Gracias. – foxdonut

8

editar: puede que ya no sea relevante.

El Clojure es enrevesado para mí. Contiene más abstracciones que necesitan ser entendidas. Este es el precio de usar funciones de orden superior, debes saber lo que significan. Entonces, en un caso aislado, el imperativo requiere menos conocimiento. Pero el poder de las abstracciones está en sus medios de combinación. Cada ciclo imperativo debe ser leído y comprendido, mientras que las abstracciones de secuencia le permiten eliminar la complejidad de un ciclo y combinar operaciones poderosas.

Yo diría además que la versión de Groovy es al menos parcialmente funcional, ya que utiliza collect, que en realidad es un mapa, una función de orden superior. También tiene algo de estado.

Así es como me gustaría escribir la versión Clojure:

(defn positions2 [coll] 
    (let [current (atom 1) 
     if-same #(if (= %1 %2) @current (reset! current (inc %3)))] 
    (map if-same (cons (first coll) coll) coll (range (count coll))))) 

Esto es muy similar a la versión maravillosa, ya que utiliza un mutable "actual", pero difiere en que no tiene un próximo/variable anterior - en lugar de usar secuencias inmutables para aquellos. Como dijo Brian, la legibilidad no es intrínseca. Esta versión es mi preferencia para este caso particular, y parece estar en algún lugar en el medio.

+0

Edité el código. No creo que sea demasiado intrincado ahora. – foxdonut

+0

Genial :) No fue una crítica de tu código, solo respondía la pregunta sobre qué estilo era más fácil de entender. ¡Ahora las publicaciones de objetivos se han movido! Jejeje –

+1

Bueno, no lo tomé como una crítica, y además, tenías razón, la primera versión/era/intrincada. :-) – foxdonut

8

Estoy de acuerdo con Timothy: introduces demasiadas abstracciones. Me reelaborado su código y terminó con:

(defn positions [coll] 
    (reductions (fn [[_ prev-score :as prev] [_ score :as curr]] 
       (if (= prev-score score) prev curr)) 
    (map vector (iterate inc 1) coll))) 

información sobre su código,

(defn use-prev [[a b]] (= a b)) 
(defn pairs [coll] (partition 2 1 coll)) 
(map use-prev (pairs coll)) 

se puede refactorizar simplemente como:

(map = coll (rest coll)) 
+0

Gracias, he editado arriba de acuerdo a su sugerencia de refactorización. – foxdonut

3

yo también estoy aprendiendo Clojure y me encanta. Pero en esta etapa de mi desarrollo, la versión de Groovy era más fácil de entender. Lo que me gusta de Clojure es leer el código y tener el "Aha!" experiencia cuando finalmente "entiendes" lo que está pasando. Lo que realmente disfrutar es la experiencia similar que ocurre unos minutos más tarde cuando se da cuenta de todas las formas en que el código podría aplicarse a otros tipos de datos sin cambios en el código. He perdido la cuenta de la cantidad de veces que he trabajado con un código numérico en Clojure y luego, un poco más tarde, pensé en cómo ese mismo código podría usarse con cadenas, símbolos, widgets, ...

La analogía que uso es sobre el aprendizaje de colores. ¿Recuerdas cuando te presentaron el color rojo? Lo entendiste muy rápido, hay todo este material rojo en el mundo.Entonces oíste el término magenta y te perdiste por un tiempo. Pero, de nuevo, después de un poco más de exposición, entendiste el concepto y tuviste una forma mucho más específica de describir un color en particular. Tienes que internalizar el concepto, tener un poco más de información en tu cabeza, pero terminas con algo más poderoso y conciso.

4

El Clojure uno es más intrincado a primera vista; aunque quizás sea más elegante. OO es el resultado para hacer que el lenguaje sea más "identificable" en el nivel superior. Los lenguajes funcionales parecen tener una sensación más "algorítmica" (primitiva/elemental). Eso es exactamente lo que sentí en este momento. Tal vez eso cambie cuando tenga más experiencia trabajando con clojure.

Me temo que nos estamos adentrando en el juego de qué idioma puede ser el más conciso o resolver un problema en la menor línea de código.

La cuestión son 2 pliegues para mí:

  1. ¿Qué tan fácil a primera vista para tener una idea de lo que el código está haciendo ?. Esto es importante para los mantenedores de código.

  2. ¿Qué tan fácil es adivinar la lógica detrás del código ?. Demasiado prolijo/prolijo? Demasiado corto?

"Haz que todo sea lo más simple posible, pero no más simple".

Albert Einstein

+0

No creo que se trate de las menos líneas de código. Al menos esa no es mi intención. Los problemas son más bien aquellos que usted enumeró elegantemente. Por ejemplo, prefiero mi versión del código en lugar de la versión más corta, pero menos legible (en mi humilde opinión) de 'gnud' (en los comentarios ampliados) en este script: http://stackoverflow.com/questions/188162/ what-is-the-most-useful-script-youve-written-for-everyday-life/245724 # 245724 – foxdonut

3

Groovy soporta varios estilos de la solución de este problema también:

coll.groupBy{it}.inject([]){ c, n -> c + [c.size() + 1] * n.value.size() } 

definitivamente no refactorizado a ser bastante pero no demasiado difícil de entender.

3

Sé que esto no es una respuesta a la pregunta, pero voy a ser capaz de "comprender" el código mucho mejor si hay pruebas, tales como:

assert positions([1]) == [1] 
assert positions([2, 1]) == [1, 2] 
assert positions([2, 2, 1]) == [1, 1, 3] 
assert positions([3, 2, 1]) == [1, 2, 3] 
assert positions([2, 2, 2, 1]) == [1, 1, 1, 4] 

que me diga, un año a partir ahora, qué se espera que haga el código. Mucho mejor que cualquier versión excelente del código que he visto aquí.

¿Soy realmente? ¿Tema?

La otra cosa es que creo que la "legibilidad" depende del contexto. Depende de quién mantendrá el código. Por ejemplo, para mantener la versión "funcional" del código de Groovy (sin importar cuán brillante), tomará no solo a los programadores de Groovy, sino a los programadores Groovy funcionales ... El otro ejemplo, más relevante, es: si algunas líneas de código hace que sea más fácil de entender para los programadores Clojure "principiantes", entonces el código será más legible en general porque será entendido por una comunidad más grande: no es necesario haber estudiado Clojure durante tres años para poder captar el código y hacer edita a eso.

Cuestiones relacionadas