Clojure

De drev
Aller à : navigation, rechercher

Sommaire

requirement

install java

leiningen

Build automation with REPL and repositories

voir article detaillé leiningen

ring

Web server (embeded jetty)

https://github.com/ring-clojure/ring/wiki/Getting-Started

Emacs setup

cider

install the package `cider` available in melpa.

Add the cider plugin in the project.clj

With a buffer at the project.clj (file or dired)

  • Start a REPL with

M-x cider-jack-in

  • Connect to an existing repl with

M-x cider-connect

nrepl

In order to display results in the cider emacs buffer *cider-repl*

add nrepl [[1]] with lein, in

 ~/.lein/profiles.clj

add the key

 :plugins [[cider/cider-nrepl "0.11.0"]

to user

example :

{:user
 {:plugins [[cider/cider-nrepl "0.11.0"]]}
}

Navigation

https://github.com/clojure-emacs/cider#using-cider-mode

Jump to definition

M-.

Return back to location before jump

M-,

Evaluate

Via the toolmenu Cider/Eval there is a list of functions that evaluate code. Notablty:

  • evaluate last sexp c-x c-e

evaluate the expressions to the left of dot

example: given the following statement

(get {:a 0 :b {:c "ho hum"}} :a)
(get {:a 0 :b {:c "ho hum"}■} :a)
=>{:c "ho hum"}
(get {:a 0 :b {:c "ho hum"}}■ :a)
=>{:a 0 :b {:c "ho hum"}}
(get {:a 0 :b {:c "ho hum"}} :a)■
=> 0
  • evaluate file c-x c-k

emacs will prompt you to save the file

  • evaluate at dot C-M x or C-c C-c


Dès qu'une ou plusieurs expressions ont été évaluée, les modifications peuvent être testées directement depuis le navigateur ou une fonction de test.

  • Interupt eval
C-c C-b


evaluation d'un fichier dans un module externe

un fichier .clj situé dans un module externe peut etre evalué dans cider a condition d'être enregistre sur le disque dur.

. Faire les modifications
. sauvegarder (si une version anterieur du fichier existe, cider ne proposera pas l'enregistrement)
. evaluer

Retour a la ligne

C-j

Documentation

http://cider.readthedocs.org/en/latest/configuration/

Modes

  • Paredit: add pair of parenthesis
  • Rainbow-delimiters: colorize parenthesis

customize parenthesis with M-x customize-group rainbow-delimiters

(add-hook 'clojure-mode-hook 'paredit-mode)
(add-hook 'clojure-mode-hook 'rainbow-delimiters-mode)

Figwheel avec NRepl

https://github.com/bhauman/lein-figwheel/wiki/Using-the-Figwheel-REPL-within-NRepl

REPL

Charger des bibliothèque

Avec la fonction require avec un paramétré le nom de la bibliothèque en tant que symbol.

cljs.user=> (require  '[cljs-time.core :as time])
nil
cljs.user=> (time/now)
#object[Object 20161024T075753]

changer de namespace

Utiliser la fonction in-ns avec le nom du namespace en tant que symbol.

exemple:

(in-ns 'dev)


Test

On peut lancer les tests avec lein `lein test` ou dans un repl

exemple de session (réalisée dans un repl clojurescript)


user> (require '[clojure.test :refer [run-tests deftest is]])
user> (deftest add-1-to-1 (is (= 2 (+ 1 1))))
user> (run-tests)
=> Testing user

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

En générale, les tests sont enregistre dans un fichier, creer un fichier cljs avec les assertions et charger son contenu avec M-x cider-load-buffer (par défaut C-c C-k)

(ns test (:require [clojure.test :refer [run-tests deftest is]]))

(deftest add-1-to-1
  (is (= 2 (+ 1 1))))

puis lancer les tests depuis le repl avec run-tests avec en paramètre le nom du namespace en tant que symbole.

user> (run-tests 'test)
=> Testing test

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
nil

Pour lancer un seul test:

(clojure.test/test-vars [#'test/add-1-to-1])

The language

Memory management

Clojure keeps internally variable and functions references.

for example:

(defn with-name [obj name] (assoc obj :name name))
(with-name {} "olivier")

if the function with-name is evaluated with c-x c-p the error "Unable to resolve symbol: a in this context" will be triggered. The function needs to be register first by evaluating the line that define the function beforehand or evaluating the whole file with

c-x c-k

Paradigm

the first element of an expression is always a function

(function-name arg1 arg2 ...)

Arguments en ligne de commande

Sequantiel

(defn -main "Entry point " [& args]
  (println (first args) (second args))
)

Nommés, Sans plugins

On peut utiliser une déstructuration

lein run -f test -s 70
(defn -main "Entry point " [& {:as args}]
  (println args)
  (println (get args "-f"))
)

=>{-s 70, -f test}
test

Nommés, avec tools/cli

[2]

[org.clojure/tools.cli "0.3.3"]

exit

(System/exit 0)

Data Structures

hash map

key-value pairs

  • must be even
  • can be mixed
  • not to be confused with the map function
{:A true nil name "ok" 42}
=>{:A true, nil name, "ok" 42}

Use case

Update all values of a hashmap

(defn update-values [m f & args]
 (reduce (fn [r [k v]] (assoc r k (apply f v args))) {} m))

;; Example with the str function
(update-values {:id 1 :col 2} str)
=> {:id "1", :col "2"}


source http://blog.jayfields.com/2011/08/clojure-apply-function-to-each-value-of.html

Conversion de types

Avec la fonction into

exemple: conversion de vector en hash-map

(def cloth [:com :foot])
(into {} #{cloth})
=> {:com :foot}

Vector vers List

Avec la fonction lazy-seq.

(lazy-seq [:a :b :c])
=>(:a :b :c)

Atom

swap!

  • With conj
dev> (def a (atom [1 2 3]))
#'dev/a
dev> (swap! a conj 4)
[1 2 3 4]
  • Avec remove

La definition de remove est: (remove pred coll)

La collection étant le deuxième argument, on doit passer a swap! une fonction appelant remove avec la collection en paramètre %

dev> (def a (atom [1 2 3]))
#'dev/a
dev>(swap! a #(remove #{2} %))
(1 3)

Core functions

def

bind into a name

(def alpha ["A" "B" "C"])
alpha
=>["A" "B" "C"]

str

concatenate into string

(str "A" "B" "C" 1 2 3) 
=> "ABC123"

map

map f coll

Map each element of a collection to a function

the first argument is a function which will receive each element of coll one by one

(map (fn [el] (* el el)) (list 1 2 3 4))
;;or 
(map #(* % %) (list 1 2 3 4))

A function that returns a function (hi-order function) can be use as the first parameter of map.


map sur un hash

Utiliser into {} pour conserver le type hash

(->> (map (fn [[k v]]
       (prn k " and " v)
        {k v})
        {:a :aa :b :bb})
        (into {}))
:a " and " :aa
:b " and " :bb
{:a :aa, :b :bb}
Cas d'utilisations

Le premier argument de map étant une fonction, on peut utiliser une fonction de haut rang en tant que parametre

  • keyword

Les keywords sont des fonctions

(def  l '({:id 42 :name "N"} {:id 43 :name "O"}))
(map :id l)
;=> (42 43)
  • partial
(map (partial str ">") ["Roy" "Cactus" "42"])
;=>(">Roy" ">Cactus" ">42")
(map #(str ">" %) ["Roy" "Cactus" "42"])
;=>(">Roy" ">Cactus" ">42") 
  • juxt
(map (juxt :id :name) '({:id 42 :name "H2HG2"} {:id 43 :name "H3HG3"}))
;=>([42 "H2HG2"] [43 "H3HG3"])

map-indexed

(map-indexed #(println %1 %2) ["A1" "A2" "A3" "A4" "A5" "A6" "A7" "A8"])
=>0 A1
1 A2
2 A3
3 A4
4 A5
5 A6
6 A7
7 A8
(nil nil nil nil nil nil nil nil)

some

Permet de savoir si une collection contient une valeur

dev>(let [x 42
           v [1 2 3 4]]
       (some #(= x %) v))
nil
dev> (let [x 42
           v [1 2 3 4 42]]
       (some #(= x %) v))
true

get

get a value

  • in a map, from a key
(def name "chewbacca")
(def salada {:A true nil name "ok" 42})
(get salada nil)
=> chewbacca
  • in a vector, from an index
(get ["A" "B" "C" "D"] 2)
=>"C"

keywords

can be used as a function

(ifn? :a)
=>true

set

  • unique values
  • order not guaranteed
#{"A" :B 3}
=> ;B "A" 3

let

bind in a scope

(let [a 1 b 2] 
  (+ a b))
=> 3

-> Threading macro

Threads the expr, put the first argument at the begining to each rest expressions

(=
(rest (rest [1 2 3 4]))
(->  [1 2 3 4] (rest) (rest)))
=>true

->>

(=
 (->> [1 2 3 4] (take 2) )
 (take 2 [1 2 3 4]))
=>true


(def c '(1 2 3 4 5 6 7 8 9 10))
(=
  (->> c
       (drop 100)
       (take 10))

  (take 10 (drop 100 c)))
)
=> true 
'(6 7)


Il arrive que l'on ai besoin de changer l'ordre de la threading macro.

Pour changer l'ordre, créer une fonction annonye et l’appeler immédiatement

(->> {:id 42}
     (#(assoc % :ok? true )))

remove

Retirer des valeurs d'une collection

(remove #(= :a %) [:a :b :c] )
(:b :c)

ou

 (remove #{:a} [:a :b :c] )
(:b :c)

filter

  • inverse de 'remove'
(def coll  [{:id 42 :name "cactus"} {:id 43 :name "roy"}])
(first (filter #(= (:id %) 42) coll))
=> {:id 42, :name "cactus"}

Exemple: filtrer une liste de map sur une clé avec une liste d'entier

(def idlist (list 1 2 3 4))
(def maplist [{:id 1 :name "one"}, {:id 2 :name "two"}, {:id 42 :name "fourty two"}])
(filter (fn [mapelement]
               (some (fn [id] (= (:id mapelement) id))
                     idlist))
             maplist)
=>({:id 1, :name "one"} {:id 2, :name "two"})

conj

Conjoint elements to a collection

  • return a collection of the same type it's arguments
(conj (list 1 2 3 4) 5)         => (5 1 2 3 4)
(conj (sorted-set 1 2 3 4) 5)   => #{1 2 3 4 5}
(conj (hash-map :a 1 :b 2) [:c 3]) =>{:c 3 :b 2 :a 1}

cons

Construct a sequence


concat

Concat sequances

(concat (list :a :b :c) (list :d :e :f))
=> (:a :b :c :d :e :f)

can be used to "push" a list into an other list

assoc

Associate element to collection by replacing a value with an other

  • With vector, replace value at index (index must be < count)
(assoc [1 2 3 4] 1 42)
=>[1 42 3 4]
  • With map, replace (if present) or add (if not present) a key value
(assoc {"key1" "val1"  "key2" "val2"} "key1" "NEWval1")
(assoc {"key1" "val2"  "key2" "val2"} "key3" "ADDEDval")

assoc-in

(assoc-in {:did {"e40" ["P1" 9 99]}} [:did "e42"] ["P1" 2 20])
{:did {"e40" ["P1" 9 99], "e42" ["P1" 2 20]}}

update-in

On peut utiliser update-in pour associer des clé valeur dans une nested hash

(update-in {:id 42 :prop {:ok? true}} [:prop] assoc :test true)
{:id 42, :prop {:ok? true, :test true}}

merge

Merge maps. The second paremeter will erase the values of the first parameter.

(merge {:a 1 :b 2} {:a "A" :c "C"})

=> {:a "A", :b 2, :c "C"}

for

for [un-element collection] ...
(for [i (range 0 5)] (println (str "CACTUS " (inc i))))
CACTUS 1
CACTUS 2
CACTUS 3
CACTUS 4
CACTUS 5
(for [elem ["un" "deux" "trois"]] (println "-> " elem))
->  un
->  deux
->  trois
(nil nil nil)
 (concat [1 2] (for [x (range 3 10)] x))
(1 2 3 4 5 6 7 8 9)

doseq est l'équivalent non-lazy de for et prend les mêmes parametres

rename-keys

renommer des clé

dev> t
{:name "test", :a []}
dev> (clojure.set/rename-keys t {:a :b})
{:name "test", :b []}

Expression régulière

(let [p (re-pattern #"test")
                 t "this is a test ! "
                 r (re-find p t)]
             r)
"test"
(let [t "one;two;three "
                 p (re-pattern #"(\w+);(\w+);(\w+)")
                 r (re-find p t)]
             r)
["one;two;three" "one" "two" "three"]

iterate

Infinite iteration a func with a param

  • It's a good idea to use iterate with a limiter function (like take)
  • in case of infinite loop, kill the cider repl with C-c C-c in the cider repl buffer
(take 5 (iterate #(+ 3 %) 1)))
=>(1 4 7 10 13)


chaines de caracters

replace

(clojure.string/replace "roses are red" #"red" "blue")
=>"roses are blue"

Sortie écran

pprint

Where col is the collection to print

(ns example.core
  (:require
   [clojure.pprint :as pp]))

(clojure.pprint/pprint col)

Conversion de types

String vers Integer

(Integer/parseInt "42")
=>42

String vers Float

(Float/parseFloat "42")

spec

depuis la version 1.9.0

https://clojure.org/guides/spec

http://clojure.org/about/spec

http://clojure.github.io/clojure/branch-master/clojure.spec-api.html


validation de type

(:require [clojure.spec :as s])
(s/def ::user-id string?)
(s/def ::LogEntry (s/keys :req-un [::user-id]))
(s/valid? ::LogEntry {:user-id "test"})

functions

Several arguments (variatic arity)

functions can take an arbitrary number of arguments. In this case the 'right' part is separated with &

(defn vari [arg & args]
  (println (str "arg is "  arg))
  (println (str "and args are"  args))
  (- (apply + args) arg))

(vari 10 20 30)

=> arg is 10
and args are(20 30)
40


If the second argument is from a vector, apply or into needs to be called

  • The following is wrong
(def y [20 30 40]) 
(vari 10 y)
=> arg is 10
and args are([20 30 40])
NaN


  • Ok with an apply
(def y [20 30 40]) 
(apply vari 10 y)
arg is 10
and args are(20 30 40)
80
(defn f [x y & xs] (list* x y xs))

(f 1 2 ) ;; (1 2)
(f 1 2 3 4 5) ;; (1 2 3 4 5)
(f) ; => fails, need at least 2 arguments

the following function can get any number of arguments.

xs will be an clojure.lang.ArraySeq

(defn f [& xs] (list* xs))

(f)
(f 1)
(f 1 2 3)

Anonyme functions

(#(+ %1 %2) 1 2)
=>3


Multi Methode

La fonction defmulti agit comme un proxy. La valeur de retour de defmulti détermine quelle defmethod sera appelée

(defmulti check (fn [x] (:name x)))
(defmethod check "cactus" [x] (println "CACTUS ! " (:color x)))
(defmethod check "flower" [x] (println "Flower power" (:color x)))

(def cactus {:name "cactus" :color "green"})
(def flower {:name "flower" :color "yellow"})

(check cactus)
(check flower)
=>CACTUS !  green
Flower power yellow

Vocabulary

  • atom : indivisible element
  • Symbolic expression or sexpr: expression

destructuration

Dans un let

  • depuis un vecteur
(let [[a b] [1 2]] (println a b))
=> 1 2
  • depuis un hashmap
(def coll {:a 1 :b 2 :c 3})
(let [{:keys [a b c]} coll] (println a b c))
=>1 2 3
nil

En paramètre d'une fonction

hash-map

on utilise {:keys [ noms des clés ]}

les noms des clés seront transformés en symbole

(defn f [{:keys [a b c]}] (+ a b c))
(f {:a 1 :b 2 :c 3})
=>6 

vector

(def v [:a :b])
(defn f [[a b]] [b a]) 
(f v)
;=> [:b :a]

ClojureScript

text output

  (enable-console-print!)

  (time (sort (reverse (range 42))))
  (print (str 42 \newline))
  (println 42)
  (newline)

Pour afficher les guilllements on peut combiner println avec pr-str, notamment pour afficher l'app state de re-frame

(println (pr-str db))


Javascript interop

Point '.' pour faire appel a une fonction

Tiret '.-' pour faire appel a un membre

Point Point '..' pour accéder au sous fonctions

Namespace 'js' pour accéder au scope globale de JavaScript

exemples:

  • getElementById
var div = Document.getElementById("ELEMENT-ID");
let [div (.getElementById js/document "ELEMENT-ID")]
  • Screen Height
var screen-height = screen.availHeight;
(let [screen-height (.-availHeight js/screen)]
  • fonction ..
onclick="event.target.value"
:onclick #(.. % -target -value)
  • fonction .-
{:on-click #(println (.-target %))}
  • fonction .
{:on-click (fn [e](.stopPropagation e))}


  • slice
 (.slice "abc.png" 0 3)
"abc"

equivalent a

x = "abc.png"
x.slice(0, -4)
  • Instanciate
(let [parser (js/DOMParser.)]

equivalent à

var parser = new DOMParser();

Conversion de types

String vers flottant

Via la fonction javascdript parseFloat et le javascript interopt

(js/parseFloat "42E2")
;=> 4200

Exceptions

Construire une erreur

Construction d'une erreur avec js/Error. (attention au . final)

(js/Error. "Message d'erreur")

Lancer une erreur

Avec la Fonction throw "Message"

(throw (js/Error. "Oh no !"))

Attraper les exceptions

(try
  (throw (js/Error. "Oh no ! "))
  (catch js/Error e (.-message e)))
> "Oh no ! "

Exemple

(defn f [x] (let [minimal-requirement 42]
                   (when (< x minimal-requirement)
                     (throw (js/Error. "below minimal required value")))
                   true))

(try
  (f 33)
  (catch js/Error e (.-message e)))
>below minimal required value 

Sources

http://stackoverflow.com/questions/12655503/how-to-catch-any-javascript-exception-in-clojurescript

figwheel

lein plugin for loading on the fly clojure script code

Install

See https://github.com/bhauman/lein-figwheel

include the following :dependencies in your project.clj file.

[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]

Then include lein-figwheel in the :plugins section of your project.clj.

[lein-figwheel "0.5.0-4"]


example:

(defproject clojiten "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [org.clojure/clojurescript "1.7.170"]]
  :main ^:skip-aot clojiten.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}}
  :plugins [[lein-figwheel "0.5.0-4"]])

Start

lein figwheel

(maybe you also need to start a repl to launch a server)

Sources

https://github.com/bhauman/lein-figwheel

template

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="css/style.css" rel="stylesheet" type="text/css">
  </head>
  <body>

    <div id="app">
    </div>

    <script src="js/compiled/clojiten.js" type="text/javascript"></script>

  </body>
</html>

(ns clojiten.core
  (:require [reagent.core :as reagent]))

(enable-console-print!)
(println "Welcome to clojiten debug ! ")

(defonce app-state (reagent/atom {:text "Hello world!"}))

(defn child [name]
  [:p  (:text @app-state)  " "  name])

(defn childcaller []
  [child "Foo Bar"])


(defn main []
  (reagent/render-component [childcaller]
                            (.getElementById js/document "app")))

(main)
(reset! app-state {:text "HELLO"})

Sources

https://en.wikibooks.org/wiki/Clojure_Programming/Getting_Started

http://clojuredocs.org/

http://www.4clojure.com

https://github.com/Fanael/rainbow-delimiters

http://www.braveclojure.com