Programación funcional (Scala, Clojure, etc.)

elkaoD

Y más... la programación funcional está que echa humo señores.

ClojureC, a compiler for Clojure that targets C as a backend

Gran noticia. Vale que ya tenemos cosas como Gambit-C o Chicken Scheme, pero es incomparable un Scheme con Clojure y su toolset detrás (sobre todo cuando las propias herramientas son parte del lenguaje... bien por Clj).

Clojure cada vez se hace más grande. Se nota que tiene una aquitectura cojonuda, y lo de que no sea dependiente de ningún stack es la bomba. Espero cada vez más magia.

Como dice un comentario en Hacker News (https://news.ycombinator.com/item?id=5656603) no entiendo por qué de target C y no LLVM. Sigue siendo guay.

2
7 días después
B

Veamos, soy un pacazo pero bueno, estoy en 4clojure y he llegado hasta el 19 (he hecho alguno suelto más para ver cómo iba la recursión xD).

Ahora estoy con el 19 y no hay manera, he visto soluciones por internet pero no me gusta porque usan funciones que yo no conocía (nth, count) y quiero hacerlo acumulativo, usando lo que sé hasta ahora. El problema dice: Crea una función que encuentre el último elemento de una secuencia.

Mi solución (en mi mente al menos) es ir haciendo rest x a la secuencia (que te da los elementos 2,...n de la secuencia) hasta que rest x esté vacío.

El código:

fn ret [x] (
   if (not(empty?(rest(x)))) 
           ret (rest(x)))

Alguien sabe qué hago mal? Lo pruebo en el intérprete online y me salta con un error raro pero diría que la sintaxis está correcta, 4clojure lo único que dice es "tu solución no pasa los tests".

Gracias!

edited con lo último que he probado:

fn ret [x] (if (not(empty?(rest x ))) (ret (rest x )) (first x))

Vuelvo a editar con la solución:

spoiler

Alguien me puede explicar para qué hacían falta esos paréntesis? xD

1 respuesta
elkaoD

#122 los paréntesis hacen falta porque en Clojure la unidad básica de ejcución en la S-expresión y no los statements como en el resto de lenguajes. No son opcionales, sino que denotan:

(funcion arg1 arg2 ... argN)

En este caso, fn en sí misma es una función (que devuelve a su vez una función) por lo que necesitas "wrappearla" en paréntesis para que se ejecute y devuelva la función que defines (luego vuelvo a este concepto.)

4clojure funciona de una forma especial. Como ya habrás visto, comprueba si tu respuesta es correcta con varios casos de prueba. En el problema 19 en concreto el primer caso de prueba es:

(= (__ [1 2 3 4 5]) 5)

Lo que tú introduces se sustituye por __. Quedaría:

code) (ret (rest x )) (first x)) [1 2 3 4 5]) 5)[/code]
Igual aquí es donde estaba tu confusión. Lo que hace (fn ...) es crear una función y devolverla. Es un concepto importante: no estás definiendo la función ret(x) sino que simplemente la estás creando. Fíjate que queda...

((fn ...) [1 2 3 4 5])

...es decir, aplicar la función al argumento [1 2 3 4 5].

Sin paréntesis como ves [1 2 3 4 5] no queda fuera de la creación de la función sino dentro de esta. No sería un argumento de la función que vas a ejecutar, sino de la función fn que usas para crear tu función.

Haciendo (fuera de 4clojure) algo como...

(defn ret [x]
  (if (not (empty? (rest x)))
      (ret (rest x))
      (first x))

(ret [1 2 3 4 5])

...obtienes el mismo resultado. Fíjate que defn no sólo crea una función sino que también la define. El nombre queda disponible globalmente. (defn ...) no es más que un shorthand para (def nombre (fn ...)).

No sé si me he explicado bien. La clave es entender lo de que fn es una función que devuelve funciones. El resultado de (fn ...) es un objeto función que es el que aplicas sobre los argumentos posteriormente.

Insisto de nuevo, la imagen de fn es una función en sí misma, la que aplicas posteriormente sobre los argumentos. Y para explicarlo de otra forma (creo que ya van como 5 veces que va lo mismo de distinta forma, espero que no quede duda xD)

fn(nombre, argumentos, cuerpo) -> cuerpo(*argumentos) donde * denota "slicear" el array de argumentos. fn por tanto devuelve la función que define el cuerpo.

Tú hacías...

fn(ret, [x], cuerpo, [1 2 3 4 5])

...mientras que querías hacer...

fn(ret, [x], cuerpo)([1 2 3 4 5])

...o para más claridad añadimos paréntesis...

(fn(ret, [x], cuerpo))([1 2 3 4 5])

Redistribuye los paréntesis para que tengan aspecto de S-expresión y verás que tiene sentido.

Trivia: si fn no define el nombre (de hecho la función que devuelve fn es anónima, no tiene un nombre asociado), ¿para qué pasárselo entonces? De hecho es opcional, pero... ¿por qué lo admite siquiera?

En este caso es necesario porque la función se refiere a sí misma (recursiva) y aunque esta sea anónima "cara al público" sí que necesitas una forma de nombrarla interna (es decir, el nombre no queda asociado en el espacio de nombre global, pero si en el espacio de nombre local del cuerpo de la función).

Si la función no fuera recursiva podrías omitir el nombre.

PD: ¡me alegro de que te hayas puesto con Clojure! Te va a encantar y como matemático lo vas a ver bastante lógico (a un programador le cuesta más ver eso de que las funciones puedan devolver funciones y demás).

1 respuesta
B

#123 vale, me queda claro, poco a poco voy pillando lo de los paréntesis (donde más me cuesta todavía es en las sentencias if, when, while...). La verdad es que para lo poco que llevo me parece muy potente! Voy a preguntar ahora un par de dudas más xD:
1) Hay diferencia (conceptual) entre escribir

(fn [x] (...))

y

#(...%1%2%3...)

?
No sé si he puesto bien los paréntesis, pero me suena que si lo hago con almohadilla no se ponen por lo general que he visto.

2) No entiendo las diferencias de vectores, listas, lazy-lists, y esas cosas xD.
' ( a b c d) es una secuencia, no?
{a b c d} es un conjunto.
[a b c d] es un vector.

Me dejo algo? Sé por ejemplo que un vector es función de su índice, me pareció una manera muy elegante de verlo, y que la función count es O(1) en secuencias (creo) pero no en vectores (creo). Pero Clojure me lía, porque puedo escribir:

= [a b c d] '(a b c d)

(creo xD) y no se queja. Alguna referencia para entenderlo? De momento todo lo miro en clojure.core o algo así, la página oficial, pero allí algunas cosas las da "por sabidas".

1 respuesta
elkaoD

#124

  1. Sí, lo has puesto bien y son equivalentes (Trivia: %1 de hecho lo puedes dejar como % a secas).

  2. Pfff, has dado duro en el hueso de Clojure xD

'(a b c d) o (list a b c d) es una lista simplemente-enlazada. Conceptualmente la puedes entender como:

  • El elemento d concatenado (cons, en realidad) a la lista vacía '() = '(d)
  • El elemento c concatenado a la lista '(d) = '(c (d)) = '(c d) (ojo, los paréntesis de la segunda forma son sólo por claridad, si lo escribe así el Clojure forma otra cosa, una lista dentro de la lista).
  • El elemento b concatenado a la lista '(c d) = '(b c d)
  • El elemento a concatenado a la lista '(b c d) = '(a b c d)

Es decir, la lista es algo como cmd)[/cmd].

¿Qué implica esto? Las únicas operaciones "naturales" sobre la lista son:

  • (first lista) -> toma el primer elemento (la cabeza) de la lista.
  • (cons elemento lista) -> añade elemento a la cabeza de lista (lo que hemos hecho en las operaciones para construirla uno por uno).
  • (rest lista) -> toma la cola de la lista, es decir, deshace el último cons sobre esta.

Contar una lista por ejemplo no es una operación natural. La única forma de contar una lista es hacer (rest ...) recursivamente hasta que encuentras ' () y el número de iteraciones que has dado es la longitud de esta.

En efecto además de eso las listas son secuencias... pero ojo, una secuencia en Clojure no es más que un "contrato" (o interfaz). Cuando una estructura de datos es una secuencia significa que se pueden realizar las operaciones que comentaba antes que soporta la lista. Se podría decir que una lista es la estructura de datos más simple que implementa la interfaz "secuencia".

Hay otras estructuras de datos que también son secuencias, por ejemplo las listas infinitas (o lazy lists, que ya verás en profundidad en 4clojure creo). No son listas (es imposible tener una lista infinita técnicamente) sino un algoritmo que define dado un elemento su siguiente (y de ahí se deriva la lista), pero tienen el mismo interfaz con first, next, etc.

Es importante distinguir entre estructura e interfaces. La estructura es cómo se organiza internamente, mientras que las interfaces es cómo se puede acceder a estos datos. Una misma estructura puede tener varios interfaces.

Como bien dices [a b c d] es un vector y se diferencia de la lista tanto en su estructura interna (un árbol 32-ario creo) como en el contrato o interfaz (IPersistentVector). La interfaz del vector permite acceder a los elementos por índice, añadir al final del vector, y lo más importante: es una estructura contada. Hacer (count mi-vector) es prácticamente gratis (es un valor guardo en la propia estructura).

Es difícil ver la separación entre estructura e interfaz porque muchas veces van ligadas (no tiene sentido una lista indexada), pero lo verás mejor con los sets.

#{a b c d} sería un conjunto (un hash set). La estructura de datos interna es una tabla hash que relaciona cada elemento consigo mismo. Si al acceder a un elemento (calcula el hash y busca en la tabla) lo encuentra, pues el elemento está en el conjunto. Como operaciones naturales tienes adición y eliminación y las típicas de conjuntos: conjunción, disyunción, intersección....

Con el mismo interfaz pero un pequeño detalle, tenemos los sorted-sets de la forma (sorted-set ...). La estructura detrás no es una tabla hash sino un vector, por lo que el conjunto está ordenado (y por tanto implementa una interfaz para acceder a sus elementos de forma ordenada aparte de la interfaz normal de los sets).

Cuidado, sin la almohadilla # las llaves {} no son un set, sino un mapa (te confundiste). Un mapa es una función que relaciona cada elemento impar con su correspondiente par: {:a 1 :b 2}, dado :a devuelve 1. Como los vectores, son funciones de sí mismos (aunque no necesariamente, esto también es sugar tanto en vectores como aquí, hay alternativas en forma de función). Como estructura detrás tienen una tabla hash pero no se relacionan a sí mismos como el hash-set, sino que relacionan cada elemento con su valor asociado. Como operaciones naturales: añadir, quitar y acceder.

Lo de que haya la notación de ' (), #{}, [], etc. no es más que por conveniencia. Todas tienen alternativa: (list ...), (hash-set ...), (vector ...) y no son más que "azúcar sintáctico".

"y que la función count es O(1) en secuencias (creo) pero no en vectores (creo)"

Como comento antes, al revés.

"porque puedo escribir: = [a b c d] ' (a b c d)"

Supongo que te refieres a (= [a b c d] '(a b c d)) (ojo con los paréntesis, ¡es una S-expresión y son necesarios!).

En efecto, puedes hacerlo y el resultado es true. La cuestión es que la función = usa por detrás nada más y nada menos que la interfaz secuencia:

  • Si (not= (first a) (first b)) -> devuelve false
  • Else (recursion (rest a) (rest b))

Los vectores si no me equivoco también implementan esta interfaz secuencia (puedes hacer (rest ...) a un vector) aunque no son muy buenos en ello: tienen que construir el vector resultado completo (en realidad usan truquitos para optimizar, pero nada como una lista pura para hacer (rest ...)) Del mismo modo, a una lista puedes hacerle (nth ...) aunque será lenta de cojones (en concreto O(N), tiene que hacer N-1 rests + 1 first).

Por otro lado las estructuras las puedes "castear". (vec '(1 2 3 4)) devuelve un vector, (seq [1 2 3 4]) devuelve una secuencia (pero esto obviamente es lento de cojones, tiene que hacer las estructuras desde cero).

No he entrado en cuales son más eficientes ni nada de eso por no liarme, pero vamos, la información está por la internet. Aún así mientras que entiendas cuáles son sus operaciones naturales te da un poco igual la eficiencia: las naturales son más eficientes por razones obvias.

De momento ni te preocupes por esta mierda, ya lo aprenderás con el lenguaje. Con que te quedes con el big-picture que te acabo de hacer, todo ok.

Aquí más en profundidad: http://clojure.org/data_structures

Referencias necesarias: la página oficial (MUCHA info, de lo mejorcito para entender el core del lenguaje), la guía rápida de referencia (la lista con las funciones más usadas) y clojuredocs. Creo que están todos en #1.

TRIVIA: para las listas hay que hacer ' (1 2 3 4...) con la comilla simple. ¿Por qué no (1 2 3 4) a secas?

Las S-expresiones también son listas (Lisp es homoicónico, datos y código son lo mismo). La única diferencia es que cuando estás programando se asume que el primer elemento de la lista es la función que se aplicará sobre los argumentos. La comilla le dice al Reader de Clojure que la lista que viene despues no hay que ejecutarla, que es una lista y punto.

EDIT: ¡IMPORTANTE! Casi se me olvida. Clojure te permite "interactuar" con las estructuras para ver sus características. P.ej. (seq? ...) te dice si una función es una secuencia.

Curiosamente (seq? [1 2 3 4]) = false (tiene sentido, un vector no es una secuencia) pero sin embargo, tal y como te comentaba: (sequential? [1 2 3 4]) = true (un vector tiene, entre otras una interfaz Sequential).

También puedes usar (class? ...) para ver la estructura concreta con la que te encuentras (que no sus interfaces).

2 1 respuesta
PiradoIV

#125 gracias kaoD :si:

16 días después
eisenfaust

Good news: http://engineering.prezi.com/blog/2013/05/21/elm-at-prezi/

Personalmente le eche un vistazo en su dia y la sintaxis me dejo algo frio. Veremos como evoluciona el proyecto ahora que hay cierto $SOPORTE detras.

PS: Prezi es una mierda. Los verdaderos nerds hacemos presentaciones en Racket.

http://docs.racket-lang.org/slideshow/

eisenfaust

Lisp koans, de Google (so they say). Como Ruby/Clojure koans, pero para CL (por si no habia quedado claro xD).

https://github.com/google/lisp-koans

1
12 días después
eisenfaust

Habemus nuevo logo xD

Que monada :3

elkaoD

Le quiero dar a Haskell (o F# incluso me plantería).

He estado con Learn You a Haskell pero es un poco pestiño, demasiado enfocado para novatos y no me acaba de gustar como explica.

¿Alguna recomendación?

2 respuestas
BLZKZ

#130 programming in haskell lo has mirado? Aunque si ya te leiste el learn you a haskell lo mismo se te queda corto

Echa un ojo aqui http://www.haskell.org/haskellwiki/Books

1 respuesta
F

Por si alguien quiere practicar scala en un proyecto "tangible". http://www.playframework.com/

1
elkaoD

#131 no me he leído LYAH, he parado a mitad, como te digo demasiado simplón y se me queda un poco a medias.

Le echaré un vistazo.

MTX_Anubis

#130 Estaba Carmack un port de Wolf3D a Haskell por lo mismo, quiere aprender Haskell bien (no lo que te enseñan en un libro) y preguntaba en twitter por foros donde hubiera expertos en Haskell. Mira a ver si el te puede decir algo xD

eisenfaust

Hablando de Haskell, me acaba de llegar esto al correo. IDE y tal: https://www.fpcomplete.com/business/designer-ide

1
10 días después
elkaoD

Horses for courses: choosing Scala or Clojure

Lo clava.

2
7 días después
Khanser

Me gustaria reflotar esto un poco para que el que este interesado pueda ver codigo para usar como ejemplo en sus propios proyectos scala+web.

Llevo desde Mayo trabajando en una consultora en UK para el gobierno haciendo un webapp para que la gente pueda solicitar online ayudas de dependencia (ya tenian uno, pero es inmantenible) asi que decidimos hacerlo con Scala + Play framework (MVC Web).

Teneis casi(98%) todo el codigo aqui https://github.com/CarersBeta/ClaimCapture Aunque no esta acabado el gobierno es el que ha impulsado que lo colguemos online para que cualquiera pueda pillarlo y aprender o reportar bugs, o lo que sea. El codigo que no esta online es por temas de seguridad, webservices, firmas de XML y mierdas varias.

Para cualquier duda me teneis por aqui

PD: Scala se ha convertido recientemente en el lenguaje estandar para http://digital.cabinetoffice.gov.uk/

4 2 respuestas
B

#137 wow congrats!!! y gracias!!!

F

Yo he empezado esta semana a probar Scala, aunque me esta costando pillarlo, dentro de un par me pondré a hacer algo con play.

MTX_Anubis

#137 Mola :D. Yo he remotado el proyecto de Scala en el curro aunque por desgracia no creo que pueda subir el código nunca xD

elkaoD

No sé si alguna vez lo he comentado, pero la programación para GPU siempre me ha parecido muy funcional (por aquello de la paralelización, inmutabilidad inter-hilos, etc.) mientras que el toolset tanto en CUDA como en OpenCL usa programación imperativa.

Harlan ha llegado para rellenar el hueco: OpenCL con sintaxis Lisp y estructuras de alto nivel nativas en el lenguaje.

Simulación de N-Cuerpos en Harlan

De momento se puede considerar lenguaje experimental pero tiene MUY buena pinta.

2
eisenfaust

Atlas de influencia de lenguajes xD

http://exploringdata.github.io/vis/programming-languages-influence-network/

Y consiguientes descubrimientos: http://orc.csres.utexas.edu/tutorial.shtml http://en.wikipedia.org/wiki/FL_%28programming_language%29 <3

4
27 días después
ItNaS

Hoy me he encontrado con este lenguaje que compila a Javascript: LiveScript

Es un fork de coffeescript (aunque ahora mismo son más bien primos lejanos) que ha adoptado un montón de cosas de F#. Además viene con una librería estándar muy completa a lo underscore.js llamada prelude.ls.

Ejemplos:

factorial = (n) ->
  | n <= 0 => 1
  | otherwise => n*factorial(n - 1)

autocurrying

imes = (x, y) --> x * y
times 2, 3       #=> 6 (normal use works as expected)
double = times 2
double 5         #=> 10

lista de objects

list-of-obj =
  * name: 'Alice'
    age: 23
  * name: 'Betty'
    age: 26

Personalmente me lo voy a mirar más a fondo. Pero de momento me está molando.

1 respuesta
elkaoD

#143

"que ha adoptado un montón de cosas de F# Haskell"

FTFY :P

Uno de mis lenguajes pendientes. La verdad que tiene buena pinta y ando algo quemado con lo poco que favorece la programación funcional Coffee.

Cuéntanos impresiones si lo prueba please.

1 respuesta
eisenfaust

#144 Donde ves la influencia de Haskell?

Es otro ML de palo xd

1 respuesta
elkaoD

#145 no he mirado ML en la vida xD Y sí, es clavado.

Lo decía por el estilo de programación, es el mismo (el rollito del pattern matching y tal).

B

joder si pilotáis de programación FFS!!!!!!! por ejemplo algún libro en el que se expliquen cosas como pattern matching, es decir, está claro que puedo buscar qué es ahora que conozco el término pero si no lo conozco...

2 respuestas
elkaoD

#147 la solución básicamente es que aprendas otros lenguajes (y pillando un libro, nada de tutoriales raros por internet).

P.ej., en el libro de Scala de Odersky (su creador) tienes un repaso por todo el lenguaje con un montón de... todo xD

Tirando de lenguajes nuevos aprendes conceptos que no están en lenguajes clásicos (el citado pattern matching) y te abre un poco los ojos. Hay todo un mundo más allá del if/for que facilita mucho la vida al programador.

2 1 respuesta
eisenfaust

#147 Lo idoneo seria un libro con un lenguaje cuya sintaxis no suponga mucha friccion a la hora de wrapear los conceptos de la programacion funcional.

The Little MLer, The Haskell School, Learn You A Haskell/Erlang...

SICP ya es mas avanzado (toston) y no lo recomiendo.

Otra opcion accesible seria un libro que ense;e estos conceptos en lenguajes mas mainstream como Functional Javascript de Fogus o Higher Order Perl (gratuito btw http://hop.perl.plover.com/book/pdf/HigherOrderPerl-trimmed.pdf )

2 1 respuesta
B

#149 yo es que todavía no me explico cómo sabéis programar en tantos lenguajes, de hecho miré el gráfico que pusiste y salvo los mainstream no conocía ninguno, me apunto ese libro y el que puso #148 (Scala) en septiembre al acabar exámenes me pondré con ellos a ver si sale algo por mierda que sea :D

2 respuestas