Clojure
Sommaire
- 1 requirement
- 2 leiningen
- 3 ring
- 4 Emacs setup
- 5 REPL
- 6 Test
- 7 The language
- 7.1 Memory management
- 7.2 Paradigm
- 7.3 Arguments en ligne de commande
- 7.4 exit
- 7.5 Data Structures
- 7.6 Conversion de types
- 7.7 Atom
- 7.8 Core functions
- 7.8.1 def
- 7.8.2 str
- 7.8.3 map
- 7.8.4 map sur un hash
- 7.8.5 map-indexed
- 7.8.6 some
- 7.8.7 get
- 7.8.8 keywords
- 7.8.9 set
- 7.8.10 let
- 7.8.11 -> Threading macro
- 7.8.12 ->>
- 7.8.13 remove
- 7.8.14 filter
- 7.8.15 conj
- 7.8.16 cons
- 7.8.17 concat
- 7.8.18 assoc
- 7.8.19 assoc-in
- 7.8.20 update-in
- 7.8.21 merge
- 7.8.22 for
- 7.8.23 rename-keys
- 7.8.24 Expression régulière
- 7.9 iterate
- 7.10 chaines de caracters
- 7.11 Sortie écran
- 7.12 Conversion de types
- 7.13 spec
- 8 functions
- 9 Multi Methode
- 10 Vocabulary
- 11 destructuration
- 12 ClojureScript
- 13 figwheel
- 14 Sources
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
[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.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