Джон для khjs #1

Кто такой Джон Галт?

  • Тимлид в R⚡️R
  • Соорганизатор KyivJS
  • Котан в kottans.org

Почему я делаю этот доклад?

  • Меня пригласили
  • Я в очередной раз интересуюсь ClojureScript
  • Слежу за языком и вам советую

Команда "Enterprise"

Your Generic language Java Haskell Clojure JS ––
В данной аналогии JS = двигатель корабля

ClojureScript

  • "Младший брат" Clojure
  • Clojure использует JVM / CLR / JS как хост
  • Минус вещи для работы с многопоточностью

Простота vs Легкость

Рич Хикки топит за простоту, а кложура делает её легкой

Также в номере

  • Воспитание функционального мышления
  • Фронтенд на cljs = пуля в борьбе с JS снобами
  • Иммутабельность по дефолту
  • Практичность и разумные компромиссы
  • Встраиваемость
  • Комьюнити
  • Эксперименты
  • Кладезь для идей

Синтаксис

Тут у меня if и выполнится эта ветка, но скобочек нет, значит выполнится только первое выражение из неё, а эти выражения тогда что?

/*
<Str>
  <p>hello</p>
  <p>world</p>
</Str>
*/
React.createElement(
  Str, null,
    React.createElement(
      'p', null, 'hello'
    ),
    React.createElement(
      'p', null, 'world'
    ))
;function      arg2   arg3
;↓              ↓      ↓
(+      1       2      3)
(str "hello" "world")
;       ↑
;      arg1

Вложенность

(def r 5)
(defn square [x] (* x x))

(* (.-PI js/Math) (square r))
(map #(* (.-PI js/Math)
         (square %))
      [1 2 3])

(map (fn [radius]
        (* (.-PI js/Math)
           (square radius)))
     [1 2 3])

Список

(1 2 3)
Вызов функции
(fn arg1 arg2)

Постойте…

Код = Данные

Система макросов

для расширения языка

Простые типы

  • Числа 5, 12.15, 3/10
  • Ключевые слова :when, :id, :foobar
  • Символы my-fn, my-var, +, /
  • Строки "Hello", "World"
  • Буквы \newline, \a

Основные коллекции

  • Списки (list 1 2 3), '(1 2 3)
  • Векторы (vector 1 2 3), [1 2 3]
  • Хеш-таблицы (hash-map :a 1 :b 2), {:a 1 :b 2}
  • Множества (set '(1 2 3)), #{1 2 3}

Зачастую то, что работает на одном типе коллекции – будет работать и на других

Иммутабельность

; Ignoring other *JS
(def cityjs
  {:kharkiv ["KharkivJS"]})
(assoc cityjs
  :kharkiv
  (conj
    (:kharkiv cityjs)
    "KhJS"))
; => {:kharkiv
;     ["KharkivJS" "KhJS"]}
(update-in cityjs
  [:kharkiv]
  #(conj % "KhJS"))
; => {:kharkiv 
;     ["KharkivJS" "KhJS"]}

Преимущества

  • производительность структур и их сравнения
  • общие структурные элементы

Функции и ФП

  • функции как объекты первого класса
  • композиция, частичное применение
  • поддержка нескольких арностей
  • диспатчинг по функции
(defn greet
    ([salute]
        (greet salute "world"))
    ([salute who]
        (println salute who)))
; cljs.user=> (greet "hey")
; hey world
; nil
(defmulti refactor :framework)
; same as
; (defmulti refactor
;           #(% :framework))
(defmethod refactor :angular
  [{name :name}]
  (println "Rewrite"
           name
           "in react"))
(refactor {
  :framework :angular
  :name "My TODO App"})
; Rewrite My TODO App in react
; nil

In -| transform |-> Out

Вообще, в итоге построение программы сводится к тому, чтобы придумать, какую структуру данных вы получаете на входе, какую вы хотите получить на выходе и написать трансформацию на основании функций, что превратит первое во второе

Практичность

  • Вещи, которые считаются плохими, делаются явными (мутации и !, …)
  • Доступ к хост платформе (.random js/Math)
  • При принятии решения "умное/чистое vs практичное" – выбирают практичное

Дайте мне точку входа и я весь переколбашу проект*

* – предполагаем, что у вас Leiningen

project.clj

(defproject color-clock "0.1.0-SNAPSHOT"
  :description "FIXME: write this!"
  :dependencies [
    [org.clojure/clojure "1.8.0"]
    [org.clojure/clojurescript "1.9.456"]
    [reagent "0.6.0"]]
  :jvm-opts ^:replace ["-Xmx1g" "-server"]
  :plugins [[lein-npm "0.6.1"]]
  :npm {:dependencies [[source-map-support "0.4.0"]]}
  :source-paths ["src" "target/classes"]
  :clean-targets ["out" "release"]
  :target-path "target")

Как такой получить?

  • написать руками
  • сделать lein new %template% project-name

%template%

Работа с DOM

(defn clock-page []
  (let [cur-time (r/atom (get-current-time))]
    (add-watch cur-time :seconds println)
    (fn []
      (js/setTimeout
        #(reset! сur-time (get-current-time)) 1000)
      [:div {:class "holder" 
             :style {"backgroundColor"
                     (time-to-rgb @current-time)}} 
        [:h1 (interpose ":" @current-time)]])))
(r/render [clock-page]
  (.getElementById js/document "app"))

let он и в Африке let

(let [somevar "somevalue"]
  (use-local-binding somevar))

Atom

(def answer (atom 42))
(println (deref answer))
(println @answer) ; => 42
(swap! answer #(* 2 %)) ; => 84
(compare-and-set! answer 42 13)
; => false
(reset! answer 42) ; => 42

using reagent/atom to get automagical rerendering

Markup

[:div
  {:class "holder" 
   :style {
     "backgroundColor"
      (time->rgb @cur-time)}} 
  [:h1 (interpose ":" @cur-time)
]]

Хотите просто хтмл – enlive

(ns color-clock.core
  (:require
    [reagent.core :as r]
    [goog.string :as str]))

Имя неймспейса ↔ путь к файлу
Поэтому, в примере выше src/color_clock/core.cljs

(defn clock-page []
  (let [cur-time (r/atom (get-current-time))]
    (add-watch cur-time :seconds println)
    (fn []
      (js/setTimeout
        #(reset! сur-time (get-current-time)) 1000)
      [:div {:class "holder" 
             :style {"backgroundColor"
                     (time-to-rgb @current-time)}} 
        [:h1 (interpose ":" @current-time)]])))
(r/render [clock-page]
  (.getElementById js/document "app"))

За кадром



Continue? Y/N

Джон, просто Джон

sudodoki