Введение в Clojure

для Java разработчиков

Как говориться, нам нужно то, что делает решение простых задач легким, а сложных - возможным.

Чаз Эмирик

«Программирование на Clojure»

Роман Махлин

2016г.

Agenda

  1. Факты о Clojure
  2. Success Stories
  3. Синтаксис
  4. Live demo
  5. Clojure пятью словами
    1. JVM
    2. Динамика
    3. Компиляция
    4. LISP
    5. Concurrency
  6. Java interop
  7. Платформы и сообщество
  8. Заключение
  9. Ссылки
  10. Q/A

Факты о Clojure

  • Первый релиз в 2007 году
  • Автор: Рич Хикки(Rich Hickey)
  • В Top5 любимых языков по версии stackoverflow.com на 2015г.
  • 17,500+ репозиториев на github.com

и многие другие

Success stories

Теперь мы подходим к решающему шагу в математической абстракции:

мы забываем, что обозначают наши символы.

Герман Вейль

«Математический способ мышления»

Синтаксис

  • Строки: "string"
  • Символ: \c
  • Логические значения: true false
  • NULL: nil
  • Ключевые слова: :keyword
  • Числа: 1 2 3..., 0xff, 040, 12r78, 18N, 3/4
  • Регулярные выражения: #"regex"
  • Списки: '(1 2 3)
  • Вектор: [1 2 3]
  • Ассоциативный массив: {:key1 value1 :key2 value2}
  • Множество: #{1 2 3}
  • Функции: do, def, let, defn, fn, if., loop, recur, cond, try, throw, eval, quote, reduce, var, ...
(defn fast-pow [a n]
  (cond (zero? n) 1
        (even? n) (letfn [(square [x] (*' x x))]
                    (square (fast-pow a (/ n 2))))
        :else (*' a (fast-pow a (dec' n)))))
public static double pow(double a, int b) {
    double result = 1;
    while(b > 0) {
        if (b % 2 != 0) {
            result *= a;
            b--;
        } 
        a *= a;
        b /= 2;
    }

    return result;

}

Синтаксис

Java

Clojure

(ns conway.rules)
 
(defn gen-cell[](if (> (Math/random) 0.7) :alive :dead))
 
(defn seed-grid [rows cols]
  (vec (take rows (repeatedly 
    (fn [] (vec (take cols (repeatedly gen-cell))))))))
 
(defn neighbors [[i j]]
  (let [x ((juxt inc inc identity dec dec dec identity inc) i)
        y ((juxt identity inc inc inc identity dec dec dec) j)]
    (map vector x y)))
 
(defn count-neighbors [grid coord]
  (let [n (map #(get-in grid %) (neighbors coord))]
    (count (filter #(= % :alive) n))))
 
(defn sim-step [grid coord]
  (let [n-live (count-neighbors grid coord)]
    (if (= :alive (get-in grid coord))
      (case n-live
        (2 3) :alive
        :dead)
      (if (= 3 n-live) :alive :dead))))
 
(defn step [grid]
  (into [] (for [i (range (count grid))]
          (into [] (for [j (range (count (get grid i)))]
                  (sim-step grid [i j]))))))

Синтаксис

Игра жизнь

JVM

Clojure разработана для JVM

  • Полная поддержка Java типов, кроме примитивов
  • Все в Clojure является объектом
  • Никаких врапперов - прямой доступ к Java
  • Программа на Clojure компилируется в байт-код
  • Можно вызывать Java из Clojure и даже наоборот

Динамический

Clojure - динамический язык

  • REPL
    • Read
    • Eval
    • Print
    • Loop
  • Полиморфизм функций
  • Динамическая система типов
  • Боксированные типы

Компилируемый

Clojure - компилируемый язык

Две основные фазы:

  • Read
    • Программа на clojure - валидные структуры данных на clojure
  • Eval
    • Доступны макросы

На выходе обычный jar фаил

LISP

  • Компилятор доступен в Runtime
  • Загрузка и изменение кода на лету
  • Код на Clojure - валидная структура данных
  • Маленькое ядро
  • Макросы
  • Функциональный
    • Иммутабельный
    • "Чистые функции"
    • Функции высокого порядка

Clojure это LISP

CONCURRENCY

Clojure разработан для многопоточных приложений

  • Персистентные структуры данных
  • Транзакционная память
  • Атомарные типы
  • Агенты

Три вида переменных

  • Vars - изолированны в рамках одного треда
  • Refs - синхронные переменные между тредами, все сразу видят изменения
  • Agent - Асинхронные изменения
(def bookshelf (ref #{})) 

(defn shelve[book]
  (dosync (alter bookshelf conj book))) 
(defn unshelve [book] 
   (dosync (alter bookshelf disj book)))

CONCURRENCY

Пример:

  • dosync - транзакция
  • alter - аналог alter table из БД

Пример:

(def x (agent 0))
(defn increment [c n] (+ c n))
(send x increment 5)  ; @x -> 5
(send x increment 10) ; @x -> 15
  • agent - зранит состояние
  • send - посылает значение

CONCURRENCY

(ns parallel-fetch
  (:import (java.io InputStream InputStreamReader BufferedReader)
           (java.net URL HttpURLConnection)))

(defn get-url [url]
  (let [conn (.openConnection (URL. url))]
    (.setRequestMethod conn "GET")
    (.connect conn)
    (with-open [stream (BufferedReader.
                       (InputStreamReader. (.getInputStream conn)))]
      (.toString (reduce #(.append %1 %2)
                          (StringBuffer.) (line-seq stream))))))

(defn get-urls [urls]
  (let [agents (doall (map #(agent %) urls))]
    (doseq [agent agents] (send-off agent get-url))
    (apply await-for 5000 agents)
    (doall (map #(deref %) agents))))

(prn (get-urls '("http://lethain.com" "http://willarson.com")))

Java interop

  • '.' для вызова метода объекта
  • '.-' вызов геттера
  • '/' для статического поля
  • new для создания нового инстанса

Например:

(.toUpperCase "fred")

-> "FRED"

(.getName String)

-> "java.lang.String"

(.-x (java.awt.Point. 1 2))

-> 1

(System/getProperty "java.vm.version")

-> "1.6.0_07-b06-57"

Math/PI -> 3.141592653589793

Java interop

(bean java.awt.Color/black)

-> {:RGB -16777216, :alpha 255, :blue 0, :class java.awt.Color, :colorSpace #object[java.awt.color.ICC_ColorSpace 0x5cb42b "java.awt.color.ICC_ColorSpace@5cb42b"], :green 0, :red 0, :transparency 1}

  • bean - превращает объект в мапу
  • Можно опционально указать тип данных

(defn len [x] (.length x))

(defn len2 [^String x] (.length x))

user=> (time (reduce + (map len (repeat 1000000 "asdf"))))

"Elapsed time: 3007.198 msecs" 4000000

user=> (time (reduce + (map len2 (repeat 1000000 "asdf"))))

"Elapsed time: 308.045 msecs" 4000000

Java interop

  • Отслеживание рефлексии

(set! *warn-on-reflection* true)

-> true

(defn foo [s] (.charAt s 1))

-> Reflection warning, line: 2 - call to charAt can't be resolved.

-> #user/foo

(defn foo [^String s] (.charAt s 1))

-> #user/foo

(defn hinted

    (^String [])

    (^Integer [a])

    (^java.util.List [a & args]))

-> #user/hinted

Java interop

  • Поддержка примитивов
    • функция с именем примитива возвращает этот примитив

    • функция с именем примитива во множественном числе возвращает массив примитивов

  • Огромный набор функций для поддержки операций с примитивами

static public float asum(float[] xs) {

    float ret = 0;

    for(int i = 0; i < xs.length; i++)

        ret += xs[i];

    return ret;

}

(defn asum [^floats xs]

    (areduce xs i ret (float 0)

        (+ ret (aget xs i))))

=>

Платформы и сообщество

  • Typed Clojure - типизированная Clojure, с проверкой типов на этапе компиляции
  • Clojurescript - Clojure в браузере - компилируется в javascript
  • QUIL - Processing для Clojure
  • Clojure CLR - Clojure для .NET

Каждый год проходит clojurecup - хакатон по этому языку коммандами до четырех человек со всего мира

Заключение

Clojure - функциональный и простой язык общего назначения, "новый LISP" под JVM, который позволяет делать все, что позволяет делать Java в функциональном стиле. 

pros cons










 
  • Полная совместимость с Java, JVM
  • Активно развивающийся язык, open source
  • Динамика
  • Лаконичность синтаксиса
  • Параллейность "из коробки"
  • Функциональность
  • Clojurescript
  • Огромная экосистема
  • Наследуются все минусы JVM
  • Довольно медленная процедура контрибьютинга
  • Сложность отладки
  • LISP многих пугает

Материалы

Q/A

Made with Slides.com