Clojure #1 
Introduction to clojure
Why Clojure? 
● For JVM with Java interoperability 
● Instant run/reloading 
● Functional 
● Lisp features (macros, expressive) 
● built-in STM 
● Dynamic 
● Good performance
Important tools 
● Leiningen - Clojure build tool 
● REPL, nREPL 
● Editors: IDEA (La Clojure, Cursive), Emacs, 
Light Table
Basics
Functions 
Очень простой синтаксис: 
(fn [] 
4)
Function call 
Любая функция вызывается в prefix 
нотации: 
(+ 1 1) 
В данном случае + это функция, а единицы 
это аргументы.
Expressions 
В Clojure, как практически и в Scala все 
является выражением, то есть возвращает 
значение: 
(if condition 
then-branch 
else-branch)
Literals 
● "a string" - String 
● :key - Keyword 
● 'symbol - Symbol 
● newline, c - Character 
● nil - No value 
● true, false - Booleans 
● Numbers like in Java
Data literals 
● [1 2 3] - Vector 
● {:key value :key1 value1} - Map 
● #{:key :key1} - Set 
● #() - Анонимная функция (fn)
Definitions 
Определяет “переменные”: 
(def x (+ 1 (- 3 1)))
Named functions 
(defn foo 
"This is documentation" 
[arguments] 
body) 
Параметры анонимных функций 
определяются также.
Higher order functions 
Можно в качестве параметров передавать и 
другие функции, например: 
(map inc [1 2 3]) 
Или анонимный вариант: 
(map (fn [i] (+ i 1)) [1 2 3])
Scoped definitions (let) 
Можно определить переменные, которые 
будут видны лишь внутри s-expr: 
(def sum-result 
(let [pi Math/PI] 
(/ (* pi pi) 6)))
Conrol structures
if 
Ранее уже видели, else branch должен 
обязательно присутствовать
do 
Если нужно выполнить несколько 
выражений прежде, чем что-то вернуть. 
Признак side effects: 
(do 
expr1 
expr2 
return-expression)
when 
Всегда возвращает nil. Комбинация if только 
с then branch и do. 
(when (even? 2) 
expr1 
expr2)
if-let 
Комбинация let и if с проверкой на nil/false: 
(def France {:capital "Paris"}) 
(if-let [capital (:capital France)] 
(println "Capital is " capital) 
(println "Capital is empty"))
cond 
Является альтернативой для else-if цепочек: 
(defn foo [n] 
(cond 
(> n 0) "positive" 
(< n 0) "negative" 
:else "zero"))
More Clojure basics
Function composition 
Следующий код легко может быть 
переписан: 
(fn [e] (fn1 (fn2 e))) 
Короче будет так: 
(comp fn1 fn2)
Function application 
Есть более длинная форма для вызова 
функции, ее можно использовать, например, 
в макросах: 
(apply + [1 2 3])
Namespaces 
Каждый символ определен в каком-то 
namespace, его можно задать с помощью 
вызова ns: 
(ns mylib.core)
:requre 
С помощью этого ключа можно добавить в 
namespace элементы из других namespaces: 
(ns foo 
(:require 
clojure.test 
[clojure.string :as str]))
:use 
Это сочетание :require и :refer. Использовать 
следует с осторожностью, как пример: 
(ns foo 
(:use clojure.string)) 
WARNING: replace already refers to: #'clojure.core/replace in namespace: foo, being replaced by: #'clojure.string/replace 
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: foo, being replaced by: #'clojure.string/reverse
:only 
Для того, чтобы избежать подобных 
проблем, можно использовать ключ :only 
(ns foo (:use 
[clojure.string :only [join]]))
:import 
С помощью этого ключа можно добавлять 
классы из Java: 
(ns some.foo.space 
"This is namespace doc" 
(:import (java.util Date 
GregorianCalendar)))
Clojure data structures
Lists 
Списки определяются так: 
'(1 2 3) 
'("Scala" "Kotlin" "Erlang" 
"Clojure")
Vectors 
Вектора уже ранее определяли, это аналог 
массивов в Clojure.
Sets 
Множества задаются двумя способами 
#{1 2 3} 
(set [1 2 3])
Maps 
Также уже ранее обсуждали: 
{:id 55 
:name "Clojure" 
:is-dynamic true}
Immutability 
Все структуры данных в Clojure 
неизменяемы. 
Чаще всего, вновь создаваемые, структуры 
данных используют предыдущие версии, но 
все равно это может быть медленно.
Transient 
Для performance critical single-threaded 
кусков кода, можно написать все быстрее: 
(defn vrange [n] 
(loop [i 0 v (transient [])] 
(if (< i n) 
(recur (inc i) (conj! v i)) 
(persistent! v))))
Vectors and Maps basics
get 
Для векторов достает элемент по индексу. 
Для Maps достает элемент по ключу. 
(get [1 2 3] 1) ;2 
(get {:one 1} :one) ;1 
get-in принимает вектор, и выполняет 
последовательно get 
(get-in [1 [1 2] 3] [1 1]) ;2
assoc 
Для векторов добавляет новый элемент по 
индексу (возвращает новый вектор). 
Для Maps добавляет новую пару key/value 
(assoc [] 0 1) ;[1] 
(assoc {} :key :value) 
;{:key :value}
dissoc 
Удаляет элемент по ключу в Maps: 
(dissoc {:key :value} :key) ; {}
keys/vals 
Для Maps мы можем вытащить keys и values: 
(keys {:a :b :c :d}); (:a :c) 
(vals {:a :b :c :d}); (:b :d)
merge 
Также можно объединять несколько Maps, 
перекрывая значения слева направо 
(merge {:a :x :c :x} 
{:a :b} 
{:a :c :e :f}) 
; {:a :c :c :x :e :f}
merge-with 
Если мы хотим перекрывать справа налево, 
то это тоже возможно: 
(merge-with (fn [a b] a) 
{:a :x :c :x} 
{:a :b} 
{:a :c :e :f}) 
; {:a :x :c :x :e :f}
All collections basics
cons 
Образуется от слова construct, может 
добавлять первый элемент к спискам и 
векторам 
(cons 1 [1 2]) ; [1 1 2] 
(cons 1 '(1 2)) ; (1 1 2)
conj 
Добавляет элемент туда, где это удобнее в 
плане реализации коллекции: 
(conj [1 2] 1) ; [1 2 1] 
(conj '(1 2) 1) ; (1 1 2)
concat 
Объединяет две последовательности в один 
список 
(concat [1 2] '(3 4)) ; (1 2 3 4)
disj 
Удаляет элемент из множества (и только!) 
(disj #{:a :b} :a) ; #{:b}
seq 
Превращает любую коллекцию (включая 
Java collections, arrays) в seq коллекцию. 
Что важно, пустая коллекция превращается 
в nil.
Advanced collection operations
partition 
Разбивает коллекцию на части 
определенной длины. Можно указать шаг, 
тогда части смогут перекрываться: 
(partition 2 [1 2 3 4 5]) 
; ((1 2) (3 4))
flatten 
Собирает одну коллекцию из коллекции 
коллекций: 
(flatten [a [b] [c d]]) 
; (a b c d)
frequencies 
Возвращает частоту, встречающихся 
элементов: 
(frequencies [1 2 3 2 1 2]) 
; {1 2, 2 3, 3 1}
every? 
Проверяет, что все элементы удовлетворяют 
некоторому предикату: 
(every? even? [2 4 6]) ; true 
(every? even? [1 2 3]) ; false
some 
true если хотя бы один элемент 
удовлетворяет предикату: 
(some even? [2 4 5]) ; true 
(some even? [1 5 3]) ; nil
for comprehensions 
Создает ленивую коллекцию 
(for [x (range 2) 
y (range 2)] [x y]) 
; [0 0] [0 1] [1 0] [1 1] 
(for [x (range 3) 
:while (even? x)] x) 
; [2]
doseq 
Поэтому для side effects нужно использовать 
doseq, в котором они предполагаются, и 
функция всегда возвращает nil.
Functional collections
map 
Для преобразования всех элементов 
коллекции можно использовать обычную 
функцию map: 
(defn fun [i] (+ 1 i)) 
(map fun [1 2 3])
mapcat 
Аналогично flatMap в Scala, в Clojure есть 
mapcat. 
(defn fun[i] (repeat i i)) 
(mapcat fun [1 2 3]) 
Получится (1 2 2 3 3 3)
filter and remove 
Две по сути одинаковые функции, только 
отличаются условием предиката 
(filter even? (1 2 3 4)) 
;(2 4) 
(remove even? (1 2 3 4)) 
;(1 3)
reduce 
Это тоже самое, что и foldLeft. Есть вариант, 
где первый элемент становится начальным 
значением или что-то другое: 
(reduce + [1 2 3]) ;6 
(reduce cons '() [1 2 3]) 
; (3 2 1)
reductions 
Это reduce, который сохраняет все 
промежуточные значения 
(reductions + [1 2 3]) 
; (1 3 6)
Recursion
Simple recursion 
Просто можно вызвать функцию из тела: 
(defn sum 
[[head & tail]] 
(if (nil? head) 0 
(+ head (sum tail)))
mutual recursion 
Иногда нужно, чтобы две функции умели 
друг друга вызывать, на помощь приходит 
declare для второй функции. 
(declare fun-2) 
(defn fun-1 [i] 
(if (< i 3) i (fun-2 (- i 1)))) 
(defn fun-2 [i] 
(if (< i 2) i (fun-1 (- i 2))))
tail recursion 
Но если мы вызовем 
(sum (range 10000)) 
то получим StackOverflowError...
tail recursion 
Правильно использовать recur: 
(defn sum 
([[head & tail] acc] 
(if (nil? head) acc 
(recur tail (+ acc head)))) 
([coll] (sum coll 0)))
loop/recur 
Альтернативой может быть loop/recur: 
(defn sum [coll] 
(loop [[head & tail] coll 
acc 0] 
(if (nil? head) acc 
(recur tail (+ acc head)))))
Homework 
1. Напишите функцию call-twice, которая берет на вход функцию и 
параметр, и вызывает эту функцию два раза (не composition). 
2. Напишите функцию, которая читает из файла (используйте slurp), затем 
выводит текст консоль, и возвращает этот текст. 
3. Напишите def cube-anonymous, в который присвоена функция, которая 
возводит число в куб. 
4. Напишите функцию, которая на вход принимает два seq, и возвращает 
объединенные развернутые последовательности (concat + reverse) 
5. Напишите функцию, которая возвращает, есть ли элемент в seq 
(contains? не подходит, так как проверяет наличие индекса) 
6. Напишите функцию, которая по двум последовательностям выводит все 
различных элементов пары в консоль (сравнение это = или not=) 
7. Напишите функцию, которая возвращает seq повторений элемента elem 
n раз.

Clojure #1

  • 1.
  • 2.
    Why Clojure? ●For JVM with Java interoperability ● Instant run/reloading ● Functional ● Lisp features (macros, expressive) ● built-in STM ● Dynamic ● Good performance
  • 3.
    Important tools ●Leiningen - Clojure build tool ● REPL, nREPL ● Editors: IDEA (La Clojure, Cursive), Emacs, Light Table
  • 4.
  • 5.
    Functions Очень простойсинтаксис: (fn [] 4)
  • 6.
    Function call Любаяфункция вызывается в prefix нотации: (+ 1 1) В данном случае + это функция, а единицы это аргументы.
  • 7.
    Expressions В Clojure,как практически и в Scala все является выражением, то есть возвращает значение: (if condition then-branch else-branch)
  • 8.
    Literals ● "astring" - String ● :key - Keyword ● 'symbol - Symbol ● newline, c - Character ● nil - No value ● true, false - Booleans ● Numbers like in Java
  • 9.
    Data literals ●[1 2 3] - Vector ● {:key value :key1 value1} - Map ● #{:key :key1} - Set ● #() - Анонимная функция (fn)
  • 10.
  • 11.
    Named functions (defnfoo "This is documentation" [arguments] body) Параметры анонимных функций определяются также.
  • 12.
    Higher order functions Можно в качестве параметров передавать и другие функции, например: (map inc [1 2 3]) Или анонимный вариант: (map (fn [i] (+ i 1)) [1 2 3])
  • 13.
    Scoped definitions (let) Можно определить переменные, которые будут видны лишь внутри s-expr: (def sum-result (let [pi Math/PI] (/ (* pi pi) 6)))
  • 14.
  • 15.
    if Ранее ужевидели, else branch должен обязательно присутствовать
  • 16.
    do Если нужновыполнить несколько выражений прежде, чем что-то вернуть. Признак side effects: (do expr1 expr2 return-expression)
  • 17.
    when Всегда возвращаетnil. Комбинация if только с then branch и do. (when (even? 2) expr1 expr2)
  • 18.
    if-let Комбинация letи if с проверкой на nil/false: (def France {:capital "Paris"}) (if-let [capital (:capital France)] (println "Capital is " capital) (println "Capital is empty"))
  • 19.
    cond Является альтернативойдля else-if цепочек: (defn foo [n] (cond (> n 0) "positive" (< n 0) "negative" :else "zero"))
  • 20.
  • 21.
    Function composition Следующийкод легко может быть переписан: (fn [e] (fn1 (fn2 e))) Короче будет так: (comp fn1 fn2)
  • 22.
    Function application Естьболее длинная форма для вызова функции, ее можно использовать, например, в макросах: (apply + [1 2 3])
  • 23.
    Namespaces Каждый символопределен в каком-то namespace, его можно задать с помощью вызова ns: (ns mylib.core)
  • 24.
    :requre С помощьюэтого ключа можно добавить в namespace элементы из других namespaces: (ns foo (:require clojure.test [clojure.string :as str]))
  • 25.
    :use Это сочетание:require и :refer. Использовать следует с осторожностью, как пример: (ns foo (:use clojure.string)) WARNING: replace already refers to: #'clojure.core/replace in namespace: foo, being replaced by: #'clojure.string/replace WARNING: reverse already refers to: #'clojure.core/reverse in namespace: foo, being replaced by: #'clojure.string/reverse
  • 26.
    :only Для того,чтобы избежать подобных проблем, можно использовать ключ :only (ns foo (:use [clojure.string :only [join]]))
  • 27.
    :import С помощьюэтого ключа можно добавлять классы из Java: (ns some.foo.space "This is namespace doc" (:import (java.util Date GregorianCalendar)))
  • 28.
  • 29.
    Lists Списки определяютсятак: '(1 2 3) '("Scala" "Kotlin" "Erlang" "Clojure")
  • 30.
    Vectors Вектора ужеранее определяли, это аналог массивов в Clojure.
  • 31.
    Sets Множества задаютсядвумя способами #{1 2 3} (set [1 2 3])
  • 32.
    Maps Также ужеранее обсуждали: {:id 55 :name "Clojure" :is-dynamic true}
  • 33.
    Immutability Все структурыданных в Clojure неизменяемы. Чаще всего, вновь создаваемые, структуры данных используют предыдущие версии, но все равно это может быть медленно.
  • 34.
    Transient Для performancecritical single-threaded кусков кода, можно написать все быстрее: (defn vrange [n] (loop [i 0 v (transient [])] (if (< i n) (recur (inc i) (conj! v i)) (persistent! v))))
  • 35.
  • 36.
    get Для векторовдостает элемент по индексу. Для Maps достает элемент по ключу. (get [1 2 3] 1) ;2 (get {:one 1} :one) ;1 get-in принимает вектор, и выполняет последовательно get (get-in [1 [1 2] 3] [1 1]) ;2
  • 37.
    assoc Для векторовдобавляет новый элемент по индексу (возвращает новый вектор). Для Maps добавляет новую пару key/value (assoc [] 0 1) ;[1] (assoc {} :key :value) ;{:key :value}
  • 38.
    dissoc Удаляет элементпо ключу в Maps: (dissoc {:key :value} :key) ; {}
  • 39.
    keys/vals Для Mapsмы можем вытащить keys и values: (keys {:a :b :c :d}); (:a :c) (vals {:a :b :c :d}); (:b :d)
  • 40.
    merge Также можнообъединять несколько Maps, перекрывая значения слева направо (merge {:a :x :c :x} {:a :b} {:a :c :e :f}) ; {:a :c :c :x :e :f}
  • 41.
    merge-with Если мыхотим перекрывать справа налево, то это тоже возможно: (merge-with (fn [a b] a) {:a :x :c :x} {:a :b} {:a :c :e :f}) ; {:a :x :c :x :e :f}
  • 42.
  • 43.
    cons Образуется отслова construct, может добавлять первый элемент к спискам и векторам (cons 1 [1 2]) ; [1 1 2] (cons 1 '(1 2)) ; (1 1 2)
  • 44.
    conj Добавляет элементтуда, где это удобнее в плане реализации коллекции: (conj [1 2] 1) ; [1 2 1] (conj '(1 2) 1) ; (1 1 2)
  • 45.
    concat Объединяет двепоследовательности в один список (concat [1 2] '(3 4)) ; (1 2 3 4)
  • 46.
    disj Удаляет элементиз множества (и только!) (disj #{:a :b} :a) ; #{:b}
  • 47.
    seq Превращает любуюколлекцию (включая Java collections, arrays) в seq коллекцию. Что важно, пустая коллекция превращается в nil.
  • 48.
  • 49.
    partition Разбивает коллекциюна части определенной длины. Можно указать шаг, тогда части смогут перекрываться: (partition 2 [1 2 3 4 5]) ; ((1 2) (3 4))
  • 50.
    flatten Собирает однуколлекцию из коллекции коллекций: (flatten [a [b] [c d]]) ; (a b c d)
  • 51.
    frequencies Возвращает частоту,встречающихся элементов: (frequencies [1 2 3 2 1 2]) ; {1 2, 2 3, 3 1}
  • 52.
    every? Проверяет, чтовсе элементы удовлетворяют некоторому предикату: (every? even? [2 4 6]) ; true (every? even? [1 2 3]) ; false
  • 53.
    some true еслихотя бы один элемент удовлетворяет предикату: (some even? [2 4 5]) ; true (some even? [1 5 3]) ; nil
  • 54.
    for comprehensions Создаетленивую коллекцию (for [x (range 2) y (range 2)] [x y]) ; [0 0] [0 1] [1 0] [1 1] (for [x (range 3) :while (even? x)] x) ; [2]
  • 55.
    doseq Поэтому дляside effects нужно использовать doseq, в котором они предполагаются, и функция всегда возвращает nil.
  • 56.
  • 57.
    map Для преобразованиявсех элементов коллекции можно использовать обычную функцию map: (defn fun [i] (+ 1 i)) (map fun [1 2 3])
  • 58.
    mapcat Аналогично flatMapв Scala, в Clojure есть mapcat. (defn fun[i] (repeat i i)) (mapcat fun [1 2 3]) Получится (1 2 2 3 3 3)
  • 59.
    filter and remove Две по сути одинаковые функции, только отличаются условием предиката (filter even? (1 2 3 4)) ;(2 4) (remove even? (1 2 3 4)) ;(1 3)
  • 60.
    reduce Это тожесамое, что и foldLeft. Есть вариант, где первый элемент становится начальным значением или что-то другое: (reduce + [1 2 3]) ;6 (reduce cons '() [1 2 3]) ; (3 2 1)
  • 61.
    reductions Это reduce,который сохраняет все промежуточные значения (reductions + [1 2 3]) ; (1 3 6)
  • 62.
  • 63.
    Simple recursion Простоможно вызвать функцию из тела: (defn sum [[head & tail]] (if (nil? head) 0 (+ head (sum tail)))
  • 64.
    mutual recursion Иногданужно, чтобы две функции умели друг друга вызывать, на помощь приходит declare для второй функции. (declare fun-2) (defn fun-1 [i] (if (< i 3) i (fun-2 (- i 1)))) (defn fun-2 [i] (if (< i 2) i (fun-1 (- i 2))))
  • 65.
    tail recursion Ноесли мы вызовем (sum (range 10000)) то получим StackOverflowError...
  • 66.
    tail recursion Правильноиспользовать recur: (defn sum ([[head & tail] acc] (if (nil? head) acc (recur tail (+ acc head)))) ([coll] (sum coll 0)))
  • 67.
    loop/recur Альтернативой можетбыть loop/recur: (defn sum [coll] (loop [[head & tail] coll acc 0] (if (nil? head) acc (recur tail (+ acc head)))))
  • 68.
    Homework 1. Напишитефункцию call-twice, которая берет на вход функцию и параметр, и вызывает эту функцию два раза (не composition). 2. Напишите функцию, которая читает из файла (используйте slurp), затем выводит текст консоль, и возвращает этот текст. 3. Напишите def cube-anonymous, в который присвоена функция, которая возводит число в куб. 4. Напишите функцию, которая на вход принимает два seq, и возвращает объединенные развернутые последовательности (concat + reverse) 5. Напишите функцию, которая возвращает, есть ли элемент в seq (contains? не подходит, так как проверяет наличие индекса) 6. Напишите функцию, которая по двум последовательностям выводит все различных элементов пары в консоль (сравнение это = или not=) 7. Напишите функцию, которая возвращает seq повторений элемента elem n раз.