¿Cual es el principal problema técnico
en el desarrollo de Software a gran escala?
-- Dijkstra
“El arte de la programación es el arte de organizar complejidad”
-- Tony Hoare
“Dos maneras de hacer software:
Una es hacerlo tan simple que obviamente no habrá deficiencias,
otra es hacerlo tan complicado que no hay deficiencias obvias...”
"Out of the Tar Pit" paper, 2006
By Ben Moseley & Peter Marks
“...es la principal dificultad en el desarrollo exitoso de software...”
Complejidad
Esencial
Accidental
class Hello {
public static void main(String[] args) {
System.out.
println("¡Hola Mundo!")
;
}
}
public class User {
String id;
String username;
String displayName;
}
// ACCIDENTAL COMPLEXITY follows...
// Constructor
// Setters
// Getters
// equals()
// hashCode()
// clone() ?
}
Fácil vs. Difícil
- conceptos subjetivos -
主観的 - 주관적 - غير موضوعي
¿Qué tiene que ver esto con
Clojure?
-- Rich Hickey
“Reducir complejidad accidental es un principal foco de Clojure”
Simplicidad
Lispy - Funcional - Datos - Inmutabilidad
Focalización
Interactivo - Abstracción - Macros
Practicidad
JVM, JavaScript, CLR - Perf.
Aprenda Clojure en 10 minutos
Datos atómicos
true false ;; booleans
0 123 3.14 22/3 ;; Numbers
"JavaConf 2014" ;; Strings
\a \z ;; Chars
user-name ;; Symbols
:key1 ;; keywords
Datos compuestos
[1 2 "tres"] ;; Vector
(1 2 "tres") ;; List
{:key1 "value1",
:key2 "value2" } ;; Map
#{1 2 3 4 5} ;; Set
;; Todas pueden anidarse
Sintaxis
Las estructuras de datos son el código
La "sintaxis" es la interpretación de esas estructuras
Reglas de Evaluación
Todo es evaluación de Expresiones
(no hay sentencias)
Los tipos de datos que vimos evaluan a sí mismos
Excepción: Symbols y Lists
Symbols
Se evaluan al dato al que hacen referencia
pi
user-name
concat
Listas
Son evaluadas como operaciones
(operation arg1 arg2 arg3)
(println "Hello World!")
Tres tipos de Operaciones:
Funciones
Op. "Especiales"
Macros
Operaciones: Funciones
(function-name arg1 arg2 arg3)
Eager evaluation: Primero se evalua cada uno de los argumentos, luego se aplica la función
(println "Hello " user-name)
(+ 1 2 3 4 5)
(concat [1 2 3] (range 4 10))
Operaciones: "Especiales"
Primitivas sobre las que se basan el resto de las operaciones
Operaciones que no pueden implementarse como funciones
Evaluación de args es particular para cada operación especial
(def pi 3.1415)
(def user-name "joe")
(if (baja-altura? alt)
(abrir-paracaidas!)
(log "Sigamos cayendo..."))
Java: bajaAltura(alt) ?
abrirParacaidas() :
log("Sigamos cayendo...");
def if fn let do quote var loop
set! recur throw try catch
Operaciones: Macros
Funciones que ejecutan en tiempo de compilación
Transforman expresiones
Permiten extender el languaje
Todo el poder del lenguage está disponible
Muchas operaciones de la librería estándar son macros
defn if-not and or when when-not ...
(or a b)
;; Se expande a:
(if a a b)
en Java: a ? a : b;
(or a b c)
;; se expande a:
(if a a (or b c))
(if a a (if b b c))
Ej. Liberar recursos (Java)
InputStream fis = null;
try {
fis = new FileInputStream(src)
// bla bla...
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Ej. Liberar recursos (Java 7)
// Se puede escribir así:
try (InputStream fis = new FileInputStream(src)) {
// bla bla...
}
Ej. Liberar recursos (Clojure)
(with-open [w (io/writer out-file)]
(write-data! w))
;; Se expande a... (aprox.)
(let [w (io/writer out-file)]
(try
(write-data! w)
(finally
(. w close))))
Ej. Macros para definir API REST
(defroutes secured-routes
(GET "/task-filters" []
(response (task-filters/fetch-all)))
(POST "/task-filters" request
(response (task-filters/insert
(:body request)
(auth/current-user request))))
(GET "/task-filters/:id" [id]
(response (task-filters/ensure-by-id id)))
(DELETE "/task-filters/:id" [id]
(response (task-filters/delete-by-id! id))))
Resumiendo
;; Tipos básicos
123 "hola" :key
;; Collecciones
[1 2 3 4 5]
{:key1 "value1" }
;; Listas evaluadas como operaciones
(def my-value 1) ;; special form
(concat arg1 arg2 arg3) ;; function
(and exp1 exp2) ;; macro
Funciones vs. Datos
En Clojure: Funciones y Datos separados
-- Alan Perlis
“Mejor tener 100 funciones operando sobre UNA estructura de datos, que 10 funciones sobre 10 estructuras de datos”
Homogeneidad
Funciones estándar de Clojure para manipular estructuras y sequencias de cualquier API
Más reutilización de código y de conocimiento
Que funciones y datos de distintas librerías puedan interoperar
(parse-csv in)
(to-json (parse-csv in))
(db/insert! (parse-csv in))
(data/diff (parse-csv in1)
(parse-csv in2))
(-> (parse-csv in)
db/insert!
log
to-json)
Polimorfismo: "A la carta"
Multimethods (multiple dispatch)
Protocols
Protocols
Grupo de funciones sin implementación
Diferentes impl. para cada tipo
La impl. puede estar separada de la definición del tipo
(to-json (parse-csv in))
¿Y si parse-csv devuelve JodaTime DateTime
y to-json no conoce de JodaTime?
;; JSON library:
(defprotocol JSONable
(to-json [this json]))
// JodaTime library (Java):
package org.joda.time;
class DateTime { ... }
;; Le "enseñamos" a DateTime
;; el protocolo JSON:
(extend-protocol JSONable DateTime
(to-json [dtime json]
(.writeString json
(unparse formatter dtime))
"Estado"
“...Estado [mutable] es la principal causa de complejidad...”
-- "Out of the Tarpit" paper, 2006
"Estado" en Clojure
Funciones puras y "Transparencia Referencial"
Inmutabilidad
Estado - Valor - Identidad
Funciones Puras
El resultado de una función pura depende sólo de sus argumentos
Una función pura no tiene efectos secundarios
(get-expiring-invoices)
(get-expiring-invoices invoices)
(get-expiring-invoices (now) invoices)
(expired? (now) invoice)
(filter (partial expired? (now)) invoices)
Transparencia Referencial
Fácil razonar acerca del código
Fácil de probar
Fácil de experimentar
Fácil de reutilizar y componer
Se puede cachear el resultado
Menos dependencias implícitas entre funciones
(expired? at-date invoice)
¿Y si expired? modifica al invoice?
Deja de ser pura. Perdemos transparencia referencial :-(
¿Y si invoice y at-date fuesen inmutables?
Inmutabilidad
¡Mencionada más de 110 veces!
(total de 350 páginas)
Inmutabilidad
¡En Clojure todos los valores son inmutables!
Incluyendo collecciones, con anidamiento
También los tipos definidos por el usuario (defrecord )
Las secuencias "lazy" también son inmutables
Excepción: objetos mutables de un API Java
En Java (típico en OO):
Referencias directas a objetos mutables
Identidad y Valor van juntos
El estado puede cambiar en cualquier momento
En Clojure:
Refs. indirectas a valores inmutables
Identidad: sucesión de Valores inmutables
No bloquea otros "readers"
Observers ("Listeners")
(def listeners (atom []))
(defn subscribe [listener]
(swap! listeners conj listener))
(defn fire [value]
(doseq [listener @listeners]
(listener value)))
Thread-safe
fire no bloquea a subscribe
No ConcurrentModificationException
Inmutabilidad, Ventajas:
Nos devuelve transparencia referencial
"Thread-safe" - Concurrencia sin locks
Semántica clara y útil de igualdad
Cualquier dato puede ser "key" de un mapa
...
multimethods - metadata - refs
core.typed - schemas - pattern matching
core.async - core.logic - Om/React
Datomic - transducers - clara.rules
...
“La Simpleza en una gran virtud, pero requiere
mucho trabajo lograrla
y
educación para apreciarla ...
... la Complejidad vende más!”
Dijkstra
¡Anteojos Hipster!