Apuntes

Lenguaje de programación

  • Familia: LISP (Common Lisp y Scheme)

    images/lisp.jpg Fuente: http://lisperati.com

    Fuente: http://landoflisp.com

  • Homoicónico: su sintaxis se basa en la escritura de listas:

    • Lista como código fuente: (* 3 (count '(10 20 30 40 50)))
    • Lista como colección: '(10 20 30 40 50), (quote (10 20 30 40 50)) o (list 10 20 30 40 50)
  • Interpretado: el código fuente es dado a un lector (Reader) como una secuencia de caracteres, quien crea estructuras de datos a partir de las expresiones encontradas. Las estructuras de datos creadas por el lector son entregadas al evaluador (Evaluator) quien resuelve o evalua las expresiones e imprime (Print) el resultado de evaluar las expresiones.
    images/01.png images/02.png
    images/03.png images/04.png
    Fuente: Clojure for Java Programmers por Rich Hickey
  • Evaluación de expresiones: en el código fuente se establecen expresiones las cuales son evaluadas o resueltas estrictamente. Las expresiones se escriben mediante formas:
    • Formas normales: su evaluación es literal.
      • 3.1416
      • "¡Hola mundo!"
      • {:lenguage "Clojure" :paradigma "Funcional"}
    • Formas compuestas: están conformadas por formas normales, las cuales deben ser evaluadas previamente.
      • (str "¡Hola" " " "mundo!")
      • (count [[true false] [false true])
      • (+ (* 2 3) (- 5 1) (/ 10 5))
    • Formas especiales: están conformadas por un símbolo y formas compuestas. El símbolo establece ciertas reglas de evaluación de las formas compuestas.
      • La forma especial if: (if (> (* 2 3) (+ 3 3)) [10 20 30] [40 50 60])
      • La forma especial . (dot): (.toUpperCase "!hola mundo!")
    • Formas constantes: evitan su evaluación.
      • 'x
      • '(10 20 30 40 50)
      • (quote [+ * -])
    • Macro: una forma especial que permite establecer nuevas abstracciones (de sintaxis o semántica) dentro del mismo lenguage de programación.
      • La macro let: (let [x 10 y 20] (+ (* 2 x) (* 2 y)))
      • La macro and: (and true true)
      • La macro fn: (fn [x] x)
  • Tipado Dinámico:

    • Los tipos de datos son identificados en tiempo de ejecución (evaluación o resolución).

      type es una función que regresa el tipo de dato del argumento dado mientras que first es una función que regresa el primer elemento de la secuencia dada y fn es una macro para la definición de funciones:

      => (type 456)
      java.lang.Long
      => (type \♞)
      java.lang.Character
      => (type false)
      java.lang.Boolean
      => (type "¡Hola mundo!")
      java.lang.String
      => (type [10 20 30 40 50])
      clojure.lang.PersistentVector
      => (type (/ 10 3))
      clojure.lang.Ratio
      => (type (/ 10 3.0))
      java.lang.Double
      => (type ((fn [x]
                  (first x))
                 "¡Hola mundo!"))
      java.lang.Character 
      => (type ((fn [x]
                   (first x))
                  [10 20 30 40 50]))
      java.lang.Long
      => (type (fn [x] x))
      $eval8009$fn__8010
      

      En Clojure un tipo se puede entender como una etiqueta de lo que es una expresión, sin restringir o forzar lo que está permitido hacer con la expresión. Lo anterior permite escribir expresiones que en cierto contexto no tengan sentido o impriman errores al ser evaluadas. Por ejemplo:

      => (type (fn [x] (* 2 x)))
      clojure02.core$eval8126$fn__8127
      

      Dicha expresión ha sido etiquetada como una función:

      => ((fn [x] (* 2 x)) 6)
      12
      => ((fn [x] (* 2 x)) 10)
      20
      => ((fn [x] (* 2 x)) 14)
      28
      

      Sin embargo no se restringe su correcto uso:

      => (+ 10 20 30)
      60
      => (+ 10 20 (fn [x] (* 2 x)))
      ClassCastException clojure02.core$eval8230$fn__8231 cannot be cast to java.lang.Number ...
      

      De igual manera no se restringe lo que sea x dentro de dicha función:

      => ((fn [x] (* 2 x)) "¡Hola mundo!")
      ClassCastException clojure.lang.String cannot be cast to java.lang.Number clojure.lang.Numbers.multiply ...
      
    • Sin embargo, puesto que las funciones no quedan restringidas a cierto tipo de dato en sus parámetros se pueden generalizar ciertas operaciones o ciertos algoritmos:

      => ((fn [xs]
           (list (first xs) (last xs))) "¡Hola mundo!")
      (\¡ \!)
      => ((fn [xs]
           (list (first xs) (last xs))) [10 20 30 40 50])
      (10 50)
      => ((fn [x xs] (str x xs)) \e "fghij")
      efghij
      => ((fn [x xs] (str x xs)) "abcde" "fghij")
      abcdefghij
      
  • Funcional: las funciones son ciudadanos de primera clase por lo que pueden ser usados como parámetros o valores regresados por otras funciones:

    • (apply max [5 3 7 2 4 1])
    • (map (fn [x] [x x]) [10 20 30])
    • (reduce (fn [v c] (conj v [(count v) c])) [] "HOLA")

Formas

Comentarios:

  • El carácter ; inicia un comentario hasta el fin de la línea en que se encuentra.

    => ; Comentario fuera de alguna expresión
       ((fn [xs]
         ; Comentario dentro de alguna expresión
         (first xs))
         ; Otro comentario
         ; seguido de otro comentario
         [true false true])
    true
    
  • La macro comment evita la evaluación de las formas que se encuentran en su interior, regresando nil.

    => (comment ((fn [xs]
                 (first xs))
                 [true false true]))
    nil
    

Literales:

  • Números: 456789, -345, 10/3, 45.24.
  • Booleanos: true, false.
  • Caracteres: \@, , \Z, \\, \5, \$, \space, \tab, \newline, \♛, \u265b.
  • Cadena de caracteres (string): "", " ", "5", "áéíóúÁÉÍÓÚàêĩöü", "♔♕♖♗♘♙♚♛♜♝♞♟", "¡Hola mundo!".
  • Keywords: :edad, :nacionalidad, :paradigma.
  • Vacío o nada: nil.

    • En un contexto booleano es evaluado como false:

      => (not nil)
      true
      => (if nil (* 2 10) (* 3 10))
      30
      
  • Colecciones: listas, vectores, conjuntos y mapas.

Símbolos

  • Nombres dado a valores: x, y, altura, base, +, apply, max, etc.:

    => (type +)
    clojure.core$_PLUS_
    => (type '+)
    clojure.lang.Symbol
    

Colecciones

images/05.png

"Clojure: The Art of Abstraction" por Alex Miller.

Las colecciones son abstraidas bajo las ideas de secuencia y asociación.

  • Secuencia: elementos continuos uno de otro:

    • listas: sus elementos son continuos uno de otro por lo que mantienen un orden, permitiendo su repetición.
    • vectores: sus elementos son continuos uno de otro mediante un índice (número entero positivo, empezando en 0), por lo que mantienen un orden, permitiendo su repetición.
    • conjuntos: sus elementos son continuos uno de otro sin importar su orden, impidiendo su repetición.
    • mapas: sus elementos son pares llave-valor continuos uno de otro sin importar su orden, impidiendo la repetición de la llave.
  • Asociación: elementos identificados:

    • vectores: sus elementos son identificados por un índice (número entero positivo, empezando en 0).
    • conjuntos: sus elementos son identificados por ellos mismos.
    • mapas: sus elementos son pares llave-valor, los valores son identificados por la llave.

Además las colecciones no quedan restringidas a contener elementos del mismo tipo por lo que es posible, por ejemplo, tener un solo vector conformado por números, booleanos y caracteres.

  • Listas: son una secuencia de elementos con la posibilidad de repetirse.

    • Ejemplos de listas:

      • '(10 20 30 10 20 30)
      • '(+ * - /)
      • (quote (x y z))
      • (list \a \e \i \á \é \í)
      • '((1 2 3) (4 5 6) (7 8 9))
    • La lista '(\a \e \i) puede visualizarse de la siguiente manera:

      images/06

  • Vectores:

    • Son una secuencia de elementos con la posibilidad de repetirse, donde sus elementos están indexados.
    • Son una asociación de elementos los cuales están identificados por un índice.

    • Ejemplos de vectores:

      • [10 20 30 10 20 30]
      • [true 456 \# nil]
      • [[10 20] [30 40] [50 60]]
    • El vector [\a \e \i] puede visualizarse de las siguientes maneras:

      images/07

  • Conjuntos:

    • Son una secuencia de valores irrepetibles.
    • Son una asociación de pares llave-valor, donde la llave es el mismo valor y no puede repetirse.

    • Ejemplos de conjuntos:

      • #{10 20 30 40 50}
      • #{1987 1999 2001}
      • #{[10 20] [50 60] [60 50]}.
    • El conjunto #{\a \e \i} puede visualizarse de las siguientes maneras:

      images/08

  • Mapas:

    • Son una secuencia de pares llave-valor.
    • Son una asociación de llaves y valores.
    • Una llave es cualquier otro tipo de expresión literal, no puede repetirse (con respecto a las otras llaves) y le corresponde tener asociado un único valor.
    • Un valor es cualquier otro tipo de expresión literal el cual puede repetirse.

    • Ejemplos de mapas:

      • {\a true, \e true, \i true, \o true, \u true}
      • {:dia 31 :mes "Febrero" :año 1999}
      • {:a [9 10 11] :b [19 20 21] :c [29 30 31]}
      • {[:sabado :domingo] :fin-de-semana}
      • {:fin-de-semana [:sabado :domingo]}
    • El mapa {\a \A \e \E \i \I} puede visualizarse de las siguientes maneras:

      images/09

Anidaciones

Es posible anidar las colecciones tanto como uno quiera:

  • Listas:

    • Con listas anidadas: '((true false false) () (20 25 30) (\x \y \z))
    • Con vectores anidados: '([\a \e \i] [10 20 30] [] [true false true true])
    • Con conjuntos anidados: '(#{10 20 30} #{} #{\A \B \C \D} #{false true})
    • Con mapas anidados: '({0 10 1 20 2 30} {\A \a \B \c \C \c} {})
  • Vectores:

    • Con vectores anidados: [[false false true] [] [30 30 20 20 10 10] [false false true] [\a \A \á \Á]]
    • Con listas anidadas: ['(\h \o \l \a) '(90 80 70) '() '(90 80 70)]
    • Con conjuntos anidados: [#{} #{\q \w \e \r \t \y} #{} #{25 100 50 0 75}]
    • Con mapas anidados: [{} {} {1 2 2 3 3 4 4 5} {\A \B \B \C \C \D}]
  • Conjuntos:

    • Con conjuntos anidados: #{#{10 30 50 70} #{} #{\a \e \i \o \u}}
    • Con listas anidados: #{'() '(9 6 3) '(\a \c \e \g)}
    • Con vectores anidadas: #{[5 4 3 2 1] [\h \o \l \a] [true false true] []}
    • Con mapas anidados: #{{2 4 3 6 4 8} {\a \b \b \c \c \d} {} {10 100 100 1000 1000 10000}}
  • Mapas:

    • Con mapas anidados: {:a {:x 10 :y 20 :z 30} {:x 10 :y 20 :z 30} :a}
    • Con listas anidadas: {10 '(5 0) '(0 5) 10}
    • Con vectores anidados: {\a [\á \A \Á \ä \Ä \à \À] [\á \A \Á \ä \Ä \à \À] \a}
    • Con conjuntos anidados: {true #{4 8 2 7} #{2 4 8 7} true}

Funciones

Función anónima

Su definición se establece mediante la macro fn y al carecer de nombre su uso práctico implica su aplicación inmediata, ser usada como parámetro/devolución de otras funciones o bien ser enlazada (binding) en un contexto.

=> ((fn [x] [(dec x) x (inc x)]) 10)
[9 10 11]

Función de orden superior

Es una función (anónima o no-anónima) la cual puede recibir como parámetro alguna función.

=> (((fn [base]
     (fn [altura]
       (/ (* base altura) 2))) 50) 3)
75
=> (((fn [x y]
     (fn [z]
       (+ z (max x y)))) 3 6) 10)
16

Aridad

Es importante identificar la aridad, el número de argumentos esperados, de las funciones.

  • Aridad constante: siempre esperan el mismo número de argumentos.

    Por ejemplo, la función inc siempre espera un solo argumento de tipo númerico:

    => (inc 10)
    11
    

    Sin embargo, al serle dado un número distinto de argumentos:

    => (inc)
    ... Wrong number of args (0) passed to: core/inc ...
    => (inc 10 20)
    ... Wrong number of args (2) passed to: core/inc ...
    
  • Aridad múltiple: esperan cierto número de argumentos quedando algunos de ellos como opcionales.

    Por ejemplo, la función range puede esperar de 0 a 3 argumentos de tipo númerico:

    => (range)
    (0 1 2 3 4 ... ∞)
    => (range 10)
    (0 1 2 3 4 5 6 7 8 9)
    => (range 5 10)
    (5 6 7 8 9)
    => (range 1 10 2)
    (1 3 5 7 9)
    

    Sin embargo, al serle dado un número distinto de argumentos:

    => (range 1 10 2 3)
    ... Wrong number of args (4) passed to: core/range ...
    => (range 1 10 2 3 4)
    ... Wrong number of args (5) passed to: core/range ...
    
  • Aridad variable: esperan de 0 a n número de argumentos:

    Por ejemplo, la función + puede esperar de 0 a n argumentos de tipo númerico:

    => (+)
    0
    => (+ 10)
    10
    => (+ 10 20)
    30
    => (+ 10 20 30)
    60
    => (+ 10 20 30 40)
    100
    

Bindings

Un binding (enlace o vínculo) es la asociación de un nombre con un valor dentro de cierto contexto.

defn

Establece el enlace entre un símbolo y un valor en un contexto global: el símbolo será el nómbre de una función y el valor será la definición de dicha función.

=> (defn fin-de-semana?
     [dias]
     (and (= (count dias) 2)
          (or (and (= (first dias) :sabado)
                   (= (last dias) :domingo))
              (and (= (first dias) :domingo)
                   (= (last dias) :sabado)))))
=> (fin-de-semana? [:viernes :sabado])
false
=> (fin-de-semana? [:domingo :lunes])
false
=> (fin-de-semana? [])
false
=> (fin-de-semana? [:sabado :domingo])
true
=> (fin-de-semana? [:domingo :sabado])
true
=> (defn el-antecesor-de
     [número]
     (dec número))
=> (defn el-sucesor-de
     [número]
     (inc número))
=> (defn el-antecesor-y-el-sucesor-de
     [número]
     (hash-map
       :el-antecesor (el-antecesor-de número)
       :el-sucesor (el-sucesor-de número)))
=> (el-antecesor-y-el-sucesor-de 10)
{:el-sucesor 11, :el-antecesor 9}
=> (el-antecesor-y-el-sucesor-de -20)
{:el-sucesor -19, :el-antecesor -21}

let

Establece el enlace entre un símbolo y un valor en un contexto local: el símbolo será el nombre dado a un valor (forma normal) y el valor podrá ser llamado en el cuerpo de let.

=> (defn fin-de-semana?
     [dias]
     (let [son-dos? (fn [dias]
                      (= (count dias) 2))
           son-sabado-domingo? (fn [dias]
                                 (and (= (first dias) :sabado)
                                      (= (last dias) :domingo)))
           son-domingo-sabado? (fn [dias]
                                 (and (= (first dias) :domingo)
                                      (= (last dias) :sabado)))]
       (and (son-dos? dias)
            (or (son-sabado-domingo? dias)
                (son-domingo-sabado? dias)))))
=> (fin-de-semana? [:domingo :lunes :martes])
false
=> (fin-de-semana? [])
false
=> (fin-de-semana? [:domingo :sabado])
true
=> (fin-de-semana? [:sabado :domingo])
true
=> (defn el-antecesor-y-el-sucesor-de
    [número]
    (let [el-antecesor-de (fn [número] (dec número))
          el-sucesor-de (fn [número] (inc número))
          un-mapa-con (fn [x y]
                        (hash-map :el-antecesor x
                                  :el-sucesor y))]
      (un-mapa-con (el-antecesor-de número)
                   (el-sucesor-de número))))
=> (el-antecesor-y-el-sucesor-de 10)
{:el-sucesor 11, :el-antecesor 9}
=> (el-antecesor-y-el-sucesor-de -10)
{:el-sucesor -9, :el-antecesor -11}

Condicionales

if

Macro que dada 3 formas las evalua de la siguiente manera:

  • Si la primera es resuelta como verdadero solo se evalua la segunda.
  • Si la primera es resuelta como falso solo se evalua la tercera.

Ejemplo:

=> (defn par-ordenado
    [xs]
    (if (not= (count xs) 2)
      xs
      (if (< (first xs) (second xs))
        xs
        (vector (second xs) (first xs)))))
=> (par-ordenado [10 20])
[10 20]
=> (par-ordenado [20 10])
[10 20]

cond

Macro que dado pares de formas las evalua de la siguiente manera:

  • Si la primera es resuelta como verdadero solo evalua la segunda.
  • Si la tercera es resuelta como verdadero solo evalua la cuarta.
  • Si la quinta es resuelta como verdadero solo evalua la sexta.
  • etc.

Ejemplo:

=> (defn la-estación-del-año-de
     [mes]
     (let [estaciones (hash-map :primavera #{:marzo :abril :mayo}
                                :verano #{:junio :julio :agosto}
                                :otoño #{:septiembre :octubre :noviembre}
                                :invierno #{:diciembre :enero :febrero})]
       (cond ((estaciones :primavera) mes) :primavera
             ((estaciones :verano) mes) :verano
             ((estaciones :otoño) mes) :otoño
             ((estaciones :invierno) mes) :invierno)))
=> (la-estación-del-año-de :noviembre)
:otoño
=> (la-estación-del-año-de :diciembre)
:invierno

case

Macro que dado una primera forma y pares de formas las evalua de la siguiente manera:

  • Si la primera es igual a la segunda evalua la tercera.
  • Si la primera es igual a la cuarta evalua la quinta.
  • Si la primera es igual a la sexta evalua la septima.
  • etc.

Ejemplo:

(defn el-siguiente-día
  [a]
  (case a
    :lunes :martes
    :martes :miércoles
    :miércoles :jueves
    :jueves :viernes
    :viernes :sabado
    :sabado :domingo
    :domingo :lunes))
=> (el-siguiente-día :miércoles)
:jueves
=> (el-siguiente-día :domingo)
:lunes

Funciones de orden superior

Clojure cuenta con algunas funciones de orden superior ya conocidas (o clásicas) dentro de cualquier lenguaje funcional. Estas funciones son conocidas ya que surgen de soluciones a problemas recurrentes en la programación.

El documento Why Functional Programming Matters (1984) de John Hughes identifica dos carácteristicas importantes de la programación funcional: funciones de orden superior y evaluación perezosa.

apply

Dada una función y una secuencia, aplica la función con los elementos de la secuencia como sus argúmentos:

=> (apply + [10 20 30 40 50])
150
=> (apply + [10 -20 30])
20
=> (apply str ["¡Hola" " " "mundo" "!"])
¡Hola mundo!
=> (apply max [10 -20 30 -40 50 -60])
50
=> (defn los-primeros-en
     [xs ys]
     (vector (first xs) (first ys)))
=> (apply los-primeros-en [[30 20] [50 60]])
[30 50]

Se puede considerar a apply como una rescritura de expresión:

  • (apply + [3 4 5]) es rescrita como (+ 3 4 5).
  • (apply max [3 4 5]) es rescrita como (max [3 4 5]).
  • (apply str ["correo" "@" "servidor" "." "com"]) es rescrita como (str "correo" "@" "servidor" "." "com").

map

Dada una función y una secuencia, aplica la función con cada uno de los elementos de la secuencia regresando una secuencia con los resultados obtenidos:

=> (map (fn [número] (* 2 número)) [])
()
=> (map (fn [número] (* 2 número)) [2 4 6 8 9])
(4 8 12 16 18)
=> (map (fn [nombre] (str "¡Hola " nombre "!")) ["A" "B" "C" "D"])
("¡Hola A!" "¡Hola B!" "¡Hola C!" "¡Hola D!")
=> (map (fn [booleano] (if (true? booleano) 1 0)) [true false false true false true])
(1 0 0 1 0 1)
=> (map count [[10 20] [30 40 50] [] [60 70 80 90] [100]])
(2 3 0 4 1)
=> (map first [[10 20] [30 40 50] [] [60 70 80 90] [100]])
(10 30 nil 60 100)

filter

Dada una función (que recibe un parámetro y regresa verdadero o falso) y una secuencia, aplica la función con cada uno de los elementos de la secuencia regresando una secuencia con los elementos para los cuales la función regresó verdadero:

=> (filter pos? [-3 2 -1 0 1 -2 3])
(2 1 3)
=> (filter neg? [-3 2 -1 0 1 -2 3])
(-3 -1 -2)
=> (defn es-mayor-a-diez-y-menor-a-veinte?
     [x]
     (and (> x 10) (< x 20)))
=> (filter es-mayor-a-diez-y-menor-a-veinte? [34 12 10 -45 26 9 79 17 20 11])
(12 17 11)
=> (defn contiene-solo-un-elemento?
     [xs]
     (= (count xs) 1))
=> (filter contiene-solo-un-elemento? [[:a :b] [:c] [] [:d :e :f] [:g]])
([:c] [:g])

La función dada a filter se llama predicado y se dice que los elementos filtrados de la secuencia cumplen el predicado.

reduce

Dada una función, opcionalmente un valor inicial, y una secuencia, aplica la función con el valor inicial obteniendo un nuevo valor. La función es aplicada una vez más, ahora con el nuevo valor y el primer elemento de la secuencia obteniendo un nuevo valor. La función es aplicada una vez más, ahora con el nuevo valor y el segundo elemento de la secuencia obteniendo un nuevo valor y así sucesivamente hasta usar todos los elementos de la secuencia.

Otro nombre para reduce es fold.

=> (reduce + 0 [1 2 3 4 5])
15

Ya que + es una función de aridad múltiple no requiere del valor inicial, el 0:

=> (reduce + [1 2 3 4 5])
15

Aunque esto permite iniciar la suma desde cualquier otra cantidad:

=> (reduce + -40 [1 2 3 4 5])
-25
=> (reduce + 100 [1 2 3 4 5])
115

Haciendo uso de una función que sea de aridad múltiple:

=> (reduce (fn
             ([] 0)
             ([x y] (+ x y)))
           [])
0
=> (reduce (fn
             ([] 0)
             ([x y] (+ x y)))
           [10])
10
=> (reduce (fn
             ([] 0)
             ([x y] (+ x y)))
           [10 20])
30
=> (reduce (fn
             ([] 0)
             ([x y] (+ x y)))
           [10 20 30])
60

Debe observarse que la función de aridad múltiple ha de esperar cero argumentos o dos argumentos.

=> (reduce (fn
             ([] 0)
             ([x xs] (+ x (count xs))))
           [])
0
=> (reduce (fn
              ([] 0)
              ([x xs] (+ x (count xs))))
            0 [ [4] ])
1
=> (reduce (fn
              ([] 0)
              ([x xs] (+ x (count xs))))
            0 [ [17] [8 12] ])
3
=> (reduce (fn
              ([] 0)
              ([x xs] (+ x (count xs))))
            0 [ [42] [120 73] [-32 -9 81] ])
6

Sobre el valor inicial

El valor inicial dado a reduce pueder ser visto como el estado inicial para la reducción o como un acumulador para la reducción en un uso práctico.

=> (conj #{} 10 20 30 20 40 30 50 10)
#{20 50 40 30 10}

La función conj añade a una colección, en este caso un conjunto, los elementos dados como subsiguientes parámetros, por lo tanto:

=> (reduce (fn
             ([] #{})
             ([conjunto elemento] (conj conjunto elemento)))
           #{} [ [10 20] [30] [40 50] [10 20] [30] ])
#{[40 50] [10 20] [30]}

De un conjunto vacío usado como acumulador, se obtienen las secuencias (vectores) que no existen previamente en el acumulador.

Otros ejemplos donde se hace uso del valor inicial como acumulador:

=> (reduce (fn
            ([mapa número]
              (conj mapa [número (inc número)] )))
          {} [30 -4 50 -6 70])
{30 31, -4 -3, 50 51, -6 -5, 70 71}
=> (reduce (fn
             ([mapa número]
               (conj mapa [número (inc número)] )))
           {} [30 30 30 30])
{30 31}

Namespace

La organización de un proyecto en Clojure se realiza mediante espacios de nombre (namespace), los cuales mantienen agrupados una colección de bindings y suelen corresponder a un archivo dentro del proyecto con la extensión .clj.

Una vez iniciado el REPL uno se encuentra en un namespace. Suponiendo que uno tenga el archivo proyecto/src/proyecto/core.clj y el contenido de dicho archivo sea:

(ns proyecto.core)

(defn factorial [x]
  (reduce * 1 (range 1 (inc x))))

(defn factoriales [xs]
  (map factorial xs))

Entonces, al iniciar el REPL mediante dicho archivo:

  • Para obtener el namespace actual donde uno se encuentra:

    => *ns*
    #object[clojure.lang.Namespace ... "proyecto.core"]
    
  • Para hacer uso de los bindings agrupados en el namespace actual:

    => (factorial 3)
    6
    => (factorial 5)
    120
    => (factoriales [])
    ()
    => (factoriales [0 1 2 3 4 5])
    (1 1 2 6 24 120)
    
    • Indicando el namespace del binding:

      => (proyecto.core/factorial 4)
      24
      => (proyecto.core/factoriales 0 5)
      (1 1 2 6 24 120)
      

Cabe notar que la primera línea ((ns proyecto.core)) encontrada en el archivo proyecto/src/proyecto/core.clj establece el namespace en el que uno se encontrará al iniciar el REPL.

Para hacer uso de bindings encontrados en otro namespace:

  • Indicando el namespace del binding:

    Por ejemplo el namespace clojure.string agrupa los bindings blank?, lower-case, upper-case (entre otros) que operan sobre una cadena de caracteres:

    => (clojure.string/blank? "")
    true
    => (clojure.string/blank? " ")
    true
    => (clojure.string/blank? "¡Hola mundo!")
    false
    => (clojure.string/lower-case "¡Hola mundo!")
    "¡hola mundo!"
    => (clojure.string/upper-case "¡Hola mundo!")
    "¡HOLA MUNDO!"
    
  • Haciendo uso del keyword :require con ns:

    (ns proyecto.core
      (:require [clojure.string :as str])
      (:require [proyecto.otronamespace :as otrons]))
    
    (defn f [x]
      (map str/lower-case xs))
    
    (defn g [xs]
      (filter (fn [x] (not (str/blank? x))) xs))
    

    En este caso se ha requerido el acceso a los bindings encontrados en los namespace clojure.string y proyecto.otronamespace. A su vez se ha dado un alias a los anteriores namespace mencionados: clojure.string será conocido como str y proyecto.otronamespace como otrons, de tal manera que las funciones f y g hagan uso de ellos.


Resumen de funciones

Se enlistan y ejemplifican algunas funciones en Clojure.


Recursividad

Se entiende a la recursividad como la capacidad de una función de aplicarse en su propio cuerpo, lo que conlleva a plantear la situación bajo la cual ha de detenerse ya que, de no ser así, existe la posibilidad de que la aplicación generé un ciclo infinito. A la situación que determina el fin de aplicaciones sucesivas se conoce como caso base.

=> (defn el-producto-de
     [x y]
     (if (zero? y)
       0
       (+ x (el-producto-de x (dec y)))))
=> (el-producto-de 3 4)
12

letfn

Macro que permite definir una función no-anónima en el contexto local de otra función, con la posibilidad de que la función no-anónima se aplique a si misma u a otra función no-anónima en el contexto local.

=> (defn el-producto-de
     [x y]
     (letfn [(sumatoria
               [inicio fin sumando total]
               (if (= inicio fin)
                 total
                 (sumatoria (inc inicio)
                            fin
                            sumando
                            (+ sumando total))))]
       (sumatoria 0 x y 0)))
=> (el-producto-de 3 2)
6
=> (el-producto-de 0 5)
0
=> (el-producto-de 6 1)
6
=> (defn la-suma-de-números-en
     [un-vector]
     (letfn [(sumatoria
               [inicio fin secuencia total]
               (if (= inicio fin)
                 total
                 (sumatoria (inc inicio)
                            fin
                            (rest secuencia)
                            (+ (first secuencia) total))))]
       (sumatoria 0 (count un-vector) un-vector 0)))
=> (la-suma-de-números-en [])
0
=> (la-suma-de-números-en [10])
10
=> (la-suma-de-números-en [10 20])
30
=> (la-suma-de-números-en [10 20 30 40 50])
150
=> (la-suma-de-números-en [10 -20 30])
20

Recursividad sobre colecciones

La recursividad permitirá operar sobre los elementos de una colección dada accediendo a ellos uno por uno.

Listas

  • first y rest: acceder del primer elemento a los siguientes.

      => (let [una-lista '(10 20 30)]
           (list (first una-lista) (rest una-lista)))
      (10 (20 30))
      => (let [una-lista '(20 30)]
           (list (first una-lista) (rest una-lista)))
      (20 (30))
      => (let [una-lista '(30)]
           (list (first una-lista) (rest una-lista)))
      (30 ())
      => (let [una-lista '()]
           (list (first una-lista) (rest una-lista)))
      (nil ())
    
  • last y butlast: acceder del último elemento a los primeros.

      => (let [una-lista '(10 20 30)]
           (list (last una-lista) (butlast una-lista)))
      (30 (10 20))
      => (let [una-lista '(10 20)]
           (list (last una-lista) (butlast una-lista)))
      (20 (10))
      => (let [una-lista '(10)]
           (list (last una-lista) (butlast una-lista)))
      (10 nil)
      => (let [una-lista '()]
           (list (last una-lista) (butlast una-lista)))
      (nil nil)
    
  • cons:

    • De algo menor a algo mayor:

          => (let [una-lista '()]
               (cons 10 una-lista))
          (10)
          => (let [una-lista '(10)]
               (cons 20 una-lista))
          (20 10)
          => (let [una-lista '(20 10)]
               (cons 30 una-lista))
          (30 20 10)
      
    • De algo mayor a algo menor:

          => (let [una-lista '()]
               (cons 30 una-lista))
          (30)
          => (let [una-lista '(30)]
               (cons 20 una-lista))
          (20 30)
          => (let [una-lista '(20 30)]
               (cons 10 una-lista))
          (10 20 30)
      
  • Suma de números en una lista:

     (let [suma (fn [xs]
                     (letfn [(r [as acc]
                               (if (empty? as)
                                 acc
                                 (r (rest as) (+ acc (first as)))))]
                       (r xs 0)))]
          (suma '(10 20 30)))
     60
    

Cadenas de caracteres

  • first y rest:

      => (let [una-cadena-de-caracteres "123"]
           (list (first una-cadena-de-caracteres) (rest una-cadena-de-caracteres)))
      (\1 (\2 \3))
      => (let [una-cadena-de-caracteres "23"]
           (list (first una-cadena-de-caracteres) (rest una-cadena-de-caracteres)))
      (\2 (\3))
      => (let [una-cadena-de-caracteres "3"]
           (list (first una-cadena-de-caracteres) (rest una-cadena-de-caracteres)))
      (\3 ())
      => (let [una-cadena-de-caracteres ""]
           (list (first una-cadena-de-caracteres) (rest una-cadena-de-caracteres)))
      (nil ())
    
  • last y butlast:

      => (let [una-cadena-de-caracteres "123"]
           (list (last una-cadena-de-caracteres) (butlast una-cadena-de-caracteres)))
      (\3 (\1 \2))
      => (let [una-cadena-de-caracteres "12"]
           (list (last una-cadena-de-caracteres) (butlast una-cadena-de-caracteres)))
      (\2 (\1))
      => (let [una-cadena-de-caracteres "1"]
           (list (last una-cadena-de-caracteres) (butlast una-cadena-de-caracteres)))
      (\1 nil)
      => (let [una-cadena-de-caracteres ""]
           (list (last una-cadena-de-caracteres) (butlast una-cadena-de-caracteres)))
      (nil nil)
    
  • srt:

    • Del inicio al fin:

         => (let [una-cadena-de-caracteres ""]
              (str una-cadena-de-caracteres \1))
         1
         => (let [una-cadena-de-caracteres "1"]
              (str una-cadena-de-caracteres \2))
         12
         => (let [una-cadena-de-caracteres "12"]
              (str una-cadena-de-caracteres \3))
         123
      
    • Del fin al inicio:

         => (let [una-cadena-de-caracteres ""]
              (str \1 una-cadena-de-caracteres))
         1
         => (let [una-cadena-de-caracteres "1"]
              (str \2 una-cadena-de-caracteres))
         21
         => (let [una-cadena-de-caracteres "21"]
              (str \3 una-cadena-de-caracteres))
         321
      
  • Número de veces repetido el carácter \O en una cadena de caracteres:

     => (let [repeticiones (fn [xs]
                             (letfn [(r [as acc]
                                       (if (empty? as)
                                         acc
                                         (r (rest as) (if (= (first as) \O) (inc acc) acc))))]
                               (r xs 0)))]
          (repeticiones "¡HOLA MUNDO!"))
     2
    

Conjuntos

  • first y rest:

      => (let [un-conjunto #{10 20 30}]
           (list (first un-conjunto) (rest un-conjunto)))
      (20 (30 10))
      => (let [un-conjunto #{10 30}]
           (list (first un-conjunto) (rest un-conjunto)))
      (30 (10))
      => (let [un-conjunto #{10}]
           (list (first un-conjunto) (rest un-conjunto)))
      (10 ())
      => (let [un-conjunto #{}]
           (list (first un-conjunto) (rest un-conjunto)))
      (nil ())
    
  • last y butlast:

      => (let [un-conjunto #{10 20 30}]
           (list (last un-conjunto) (butlast un-conjunto)))
      (10 (20 30))
      => (let [un-conjunto #{20 30}]
           (list (last un-conjunto) (butlast un-conjunto)))
      (30 (20))
      => (let [un-conjunto #{20}]
           (list (last un-conjunto) (butlast un-conjunto)))
      (20 nil)
      => (let [un-conjunto #{}]
           (list (last un-conjunto) (butlast un-conjunto)))
      (nil nil)
    
  • conj:

     => (let [un-conjunto #{}]
          (conj un-conjunto 10))
     #{10}
     => (let [un-conjunto #{10}]
          (conj un-conjunto 20))
     #{20 10}
     => (let [un-conjunto #{10 20}]
          (conj un-conjunto 30))
     #{20 30 10}
    
  • Suma de números en un conjunto:

     => (let [suma (fn [xs]
                     (letfn [(r [as acc]
                               (if (empty? as)
                                 acc
                                 (r (rest as) (+ acc (first as)))))]
                       (r xs 0)))]
          (suma #{10 20 30}))
     60
    

Vectores

  • first y rest:

      => (let [un-vector [10 20 30]]
           (list (first un-vector) (rest un-vector)))
      (10 (20 30))
      => (let [un-vector [20 30]]
           (list (first un-vector) (rest un-vector)))
      (20 (30))
      => (let [un-vector [30]]
           (list (first un-vector) (rest un-vector)))
      (30 ())
      => (let [un-vector []]
           (list (first un-vector) (rest un-vector)))
      (nil ())
    
  • last y butlast:

      => (let [un-vector [10 20 30]]
           (list (last un-vector) (butlast un-vector)))
      (30 (10 20))
      => (let [un-vector [10 20]]
           (list (last un-vector) (butlast un-vector)))
      (20 (10))
      => (let [un-vector [10]]
           (list (last un-vector) (butlast un-vector)))
      (10 nil)
      => (let [un-vector []]
           (list (last un-vector) (butlast un-vector)))
      (nil nil)
    
  • conj:

      => (let [un-vector []]
           (conj un-vector 10))
      [10]
      => (let [un-vector [10]]
           (conj un-vector 20))
      [10 20]
      => (let [un-vector [10 20]]
           (conj un-vector 30))
      [10 20 30]
    
  • cons:

      => (let [un-vector []]
           (cons 10 un-vector))
      (10)
      => (let [un-vector [10]]
           (cons 20 un-vector))
      (20 10)
      => (let [un-vector [20 10]]
           (cons 30 un-vector))
      (30 20 10)
    
  • Suma de números en un vector:

     => (let [suma (fn [xs]
                     (letfn [(r [as acc]
                               (if (empty? as)
                                 acc
                                 (r (rest as) (+ acc (first as)))))]
                       (r xs 0)))]
          (suma [10 20 30]))
     60
    

Mapas

  • first y rest:

      => (let [un-mapa {:a 10 :b 20 :c 30}]
           (list (first un-mapa) (rest un-mapa)))
      ([:a 10] ([:b 20] [:c 30]))
      => (let [un-mapa {:b 20 :c 30}]
           (list (first un-mapa) (rest un-mapa)))
      ([:b 20] ([:c 30]))
      => (let [un-mapa {:c 30}]
           (list (first un-mapa) (rest un-mapa)))
      ([:c 30] ())
      => (let [un-mapa {}]
           (list (first un-mapa) (rest un-mapa)))
      (nil ())
    
  • last y butlast:

      => (let [un-mapa {:a 10 :b 20 :c 30}]
           (list (last un-mapa) (rest un-mapa)))
      ([:c 30] ([:b 20] [:c 30]))
      => (let [un-mapa {:a 10 :b 20}]
           (list (last un-mapa) (rest un-mapa)))
      ([:b 20] ([:b 20]))
      => (let [un-mapa {:a 10}]
           (list (last un-mapa) (rest un-mapa)))
      ([:a 10] ())
      => (let [un-mapa {}]
           (list (last un-mapa) (rest un-mapa)))
      (nil ())
    
  • conj:

      => (let [un-mapa {}]
           (conj un-mapa [:a 10]))
      {:a 10}
      => (let [un-mapa {:a 10}]
           (conj un-mapa [:b 20]))
      {:a 10, :b 20}
      => (let [un-mapa {:a 10 :b 20}]
           (conj un-mapa [:c 30]))
      {:a 10, :b 20, :c 30}
    
  • Suma de números en un conjunto:

     => (let [suma (fn [xs]
                     (letfn [(r [as acc]
                               (if (empty? as)
                                 acc
                                 (r (rest as) (+ acc ((first as) 1)))))]
                       (r xs 0)))]
          (suma {:a 10 :b 20 :c 30}))
     60
    

Implementación de funciones de orden superior sobre las colecciones mediante recursividad

;;;; Funciones de Orden Superior / Higher Order Functions:
;;;;     map, filter y reduce
;;;; sobre colecciones:
;;;;     listas, cadenas de caracteres, conjuntos, vectores y mapas

;;;;
;; Listas
;;;;

(defn lmap
  "Dada una función f y una lista xs regresa una lista con
   los resultados de aplicar a f sobre los elementos de xs."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (butlast as)
                 (cons (g (last as)) bs)
                 g)))]
    (r xs '() f)))

(defn lfilter
  "Dado un predicado f y una lista xs regresa una lista con
   los elementos de xs que cumplan el predicado."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (butlast as)
                 (if (true? (g (last as)))
                   (cons (last as) bs)
                   bs)
                 g)))]
    (r xs '() f)))

(defn lreduce
  "Dada una función f, un valor inicial vi y una lista xs
   regresa un valor final vf como el resultado de aplicar a
   f sobre vi y cada uno de los elementos de xs."
  [f vi xs]
  (letfn [(r [as vf g]
            (if (empty? as)
              vf
              (r (butlast as)
                 (g vf (last as))
                 g)))]
    (r xs vi f)))

;;;;
;; Cadenas de caracteres
;;;;

(defn smap
  "Dada una función f y una cadena de caracteres xs regresa
   una cadena de caracteres con los resultados de aplicar a
   f sobre los caracteres de xs."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (rest as)
                 (str bs (g (first as)))
                 g)))]
    (r xs "" f)))

(defn sfilter
  "Dado un predicado f y una cadena de caracteres xs regresa
   una cadena de caracteres con los caracteres de xs que cumplan
   el predicado."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (rest as)
                 (if (true? (g (first as)))
                   (str bs (first as))
                   bs)
                 g)))]
    (r xs "" f)))

(defn sreduce
  "Dada una función f, un valor inicial vi y una cadena de
   caracteres xs regresa un valor final vf como el resultado
   de aplicar a f sobre vi y cada uno de los caracteres de xs."
  [f vi xs]
  (letfn [(r [as vf g]
            (if (empty? as)
              vf
              (r (rest as)
                 (g vf (first as))
                 g)))]
    (r xs vi f)))

;;;;
;; Conjuntos
;;;;

(defn cmap
  "Dada una función f y un conjunto xs regresa un conjunto con
   los resultados de aplicar a f sobre los elementos de xs."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (rest as)
                 (conj bs (g (first as)))
                 g)))]
    (r xs #{} f)))

(defn cfilter
  "Dado un predicado f y un conjunto xs regresa un conjunto con
   los elementos de xs que cumplan el predicado."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (rest as)
                 (if (true? (g (first as)))
                   (conj bs (first as))
                   bs)
                 g)))]
    (r xs #{} f)))

(defn creduce
  "Dada una función f, un valor inicial vi y una lista xs
   regresa un valor final vf como el resultado de aplicar a
   f sobre vi y cada uno de los elementos de xs."
  [f vi xs]
  (letfn [(r [as vf g]
            (if (empty? as)
              vf
              (r (rest as)
                 (g vf (first as))
                 g)))]
    (r xs vi f)))

;;;;
;; Vectores
;;;;

(defn vmap
  [f xs]
  "Dada una función f y un vector xs regresa un vector con
   los resultados de aplicar a f sobre los elementos de xs."
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (rest as)
                 (conj bs (g (first as)))
                 g)))]
    (r xs [] f)))

(defn vfilter
  "Dado un predicado f y un vector xs regresa un vector con
   los elementos de xs que cumplan el predicado."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (rest as)
                 (if (true? (g (first as)))
                   (conj bs (first as))
                   bs)
                 g)))]
    (r xs [] f)))

(defn vreduce
  "Dada una función f, un valor inicial vi y un vector xs
   regresa un valor final vf como el resultado de aplicar a
   f sobre vi y cada uno de los elementos de xs."
  [f vi xs]
  (letfn [(r [as vf g]
            (if (empty? as)
              vf
              (r (rest as)
                 (g vf (first as))
                 g)))]
    (r xs vi f)))

;;;;
;; Mapas
;;;;

(defn mmap
  "Dada una función f y un mapa xs regresa un mapa con
   las llaves de xs asociadas al resultado de aplicar
   a f sobre el valor asociado a dichas llaves."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (rest as)
                 (conj bs [((first as) 0) (g ((first as) 1))])
                 g)))]
    (r xs {} f)))

(defn mfilter
  "Dado un predicado f y un mapa xs regresa un mapa con
   los elementos de xs que cumplan el predicado en su valor."
  [f xs]
  (letfn [(r [as bs g]
            (if (empty? as)
              bs
              (r (rest as)
                 (if (true? (g ((first as) 1)))
                   (conj bs [((first as) 0) ((first as) 1)])
                   bs)
                 g)))]
    (r xs {} f)))

(defn mreduce
  "Dada una función f, un valor inicial vi y un mapa xs
   regresa un valor final vf como el resultado de aplicar a
   f sobre vi y cada uno de los elementos de xs."
  [f vi xs]
  (letfn [(r [as vf g]
            (if (empty? as)
              vf
              (r (rest as)
                 (g vf (first as))
                 g)))]
    (r xs vi f)))

Composición de funciones

Se entiende a la composición de funciones como una operación que a partir de la definición de dos funciones permite obtener una nueva función, a la función así definida se conoce como función compuesta.

=> (defn el-triple-de
     [x]
     (* 3 x))
=> (defn la-mitad-de
     [x]
     (/ x 2))
=> (defn la-composición-de
     [g f]
     (fn [x] (g (f x))))

Mediante la función la-composición-de se puede definir nuevas funciones a partir de funciones ya existentes:

=> (defn el-triple-de-la-mitad-de
     [x]
     ((la-composición-de la-mitad-de el-triple-de) x))
=> (defn la-mitad-del-triple-de
     [x]
     ((la-composición-de el-triple-de la-mitad-de) x))

De esta forma:

=> (la-mitad-del-triple-de 8)
12
=> (el-triple-de-la-mitad-de 8)
12
=> (la-mitad-del-triple-de 12)
18
=> (el-triple-de-la-mitad-de 16)
24

Clojure provee de una función de orden superior para llevar a cabo la composición de funciones, comp:

=> ((comp el-triple-de la-mitad-de) 12)
18
=> ((comp la-mitad-de el-triple-de) 16)
24
=> ((comp el-triple-de el-triple-de el-triple-de) 3)
81

Se establece que:

La composición de funciones en general no es conmutativa - Wikipedia

Por lo que a partir de las siguiente funciones:

=> (defn solo-los-positivos
     [xs]
     (filter pos? xs))
=> (defn incrementados-en-cinco
     [xs]
     (map (fn [x] (+ x 5)) xs))

Se puede observar que el resultado difiere debido al orden en que se establece la composición:

=> ((comp solo-los-positivos incrementados-en-cinco) [-7 -3 2 -1 8])
(2 7 4 13)
=> ((comp incrementados-en-cinco solo-los-positivos) [-7 -3 2 -1 8])
(7 13)
=> ((comp solo-los-positivos incrementados-en-cinco) [-3])
(2)
=> ((comp incrementados-en-cinco solo-los-positivos) [-3])
()