Clojure

From drev
Jump to: navigation, search

Contents

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.

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)

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

(update-in {:a {:b {42 {:res 0 :name "rei"}}}}
                [:a :b 42]
                (fn [old new] (assoc old (count old) new))
                {:res 44 :name "jyy"})
{:a {:b {42 {:res 0, :name "rei", 2 {:res 44, :name "jyy"}}}}}

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

(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