clojure #1
DESCRIPTION
Clojure первая лекция.TRANSCRIPT
Clojure #1Introduction 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
Ранее уже видели, else branch должен обязательно присутствовать
if
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
Следующий код легко может быть переписан:
(fn [e] (fn1 (fn2 e)))
Короче будет так:
(comp fn1 fn2)
Function composition
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
Списки определяются так:
'(1 2 3)
'("Scala" "Kotlin" "Erlang"
"Clojure")
Lists
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
Для векторов достает элемент по индексу.Для Maps достает элемент по ключу.
(get [1 2 3] 1) ;2
(get {:one 1} :one) ;1
get-in принимает вектор, и выполняет последовательно get(get-in [1 [1 2] 3] [1 1]) ;2
get
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
Образуется от слова construct, может добавлять первый элемент к спискам и векторам
(cons 1 [1 2]) ; [1 1 2]
(cons 1 '(1 2)) ; (1 1 2)
cons
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 2 [1 2 3 4 5])
; ((1 2) (3 4))
partition
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:(defn fun [i] (+ 1 i))
(map fun [1 2 3])
map
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
Просто можно вызвать функцию из тела:(defn sum
[[head & tail]]
(if (nil? head) 0
(+ head (sum tail)))
Simple recursion
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))))
Но если мы вызовем(sum (range 10000))
то получим StackOverflowError...
tail recursion
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)))))
Homework1. Напишите функцию call-twice, которая берет на вход функцию и параметр, и вызывает эту функцию два раза (не composition).2. Напишите функцию, которая читает из файла (используйте slurp), затем выводит текст консоль, и возвращает этот текст.3. Напишите def cube-anonymous, в который присвоена функция, которая возводит число в куб.4. Напишите функцию, которая на вход принимает два seq, и возвращает объединенные развернутые последовательности (concat + reverse)5. Напишите функцию, которая возвращает, есть ли элемент в seq (contains? не подходит, так как проверяет наличие индекса)6. Напишите функцию, которая по двум последовательностям выводит все различных элементов пары в консоль (сравнение это = или not=)7. Напишите функцию, которая возвращает seq повторений элемента elem n раз.