Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
re-frame à la spec
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Kent OHASHI
March 29, 2018
Programming
130
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
re-frame à la spec
Introduction to ClojureScript SPA framework "re-frame" and its integration with clojure.spec.
Kent OHASHI
March 29, 2018
More Decks by Kent OHASHI
See All by Kent OHASHI
速習FP in Scala with Flix: Flix言語で親しむ純粋関数型のコード設計
lagenorhynque
0
44
"Simple Made Easy" Made Easier: Clojureに学ぶsimplicity
lagenorhynque
0
59
Property-Based Testing with test.check and clojure.spec
lagenorhynque
0
79
🐬の推し本紹介2025: 『コーディングを支える技術 ――成り立ちから学ぶプログラミング作法』
lagenorhynque
0
140
KotlinでミニマルなResult実装による関数型エラーハンドリング
lagenorhynque
0
88
Functional Calisthenics in Kotlin: Kotlinで「関数型エクササイズ」を実践しよう
lagenorhynque
1
510
関数型言語テイスティング: Haskell, Scala, Clojure, Elixirを比べて味わう関数型プログラミングの旨さ
lagenorhynque
1
330
純LISPから考える関数型言語のプリミティブ: Clojure, Elixir, Haskell, Scala
lagenorhynque
1
390
From Scala/Clojure to Kotlin
lagenorhynque
0
140
Other Decks in Programming
See All in Programming
IBM Bob でAI駆動開発『QC-Playbook』
ichirokano2
0
110
SalesForceを内製化!? ~ HR事業を支える顧客管理基盤のインフラを大公開 ~
oku053
0
180
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
790
FactoryBotをやめればRSpecは速くなるのか?
yuta_onishi_97
0
330
Railsバージョンを4倍にした話
optim
0
350
動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn
prota
1
1.9k
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
190
見えないものを探る要求要件定義に必要な基本的思考 / invisible-requirement-thinking
minodriven
15
8.5k
R8 の設定見直しでアプリサイズ削減
andpad
0
220
3回つくってわかった、 定期実行ジョブ基盤の本質情報
takai
0
460
re:Inventに行く前に知っておきたい現地参加のノウハウ
nokomoro3
0
430
全ての永続トークンを、生まれる前に消し去りたい!
sylph01
2
880
Featured
See All Featured
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
360
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
2
330
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.6k
Site-Speed That Sticks
csswizardry
13
1.6k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.6k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
220
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.6k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
35
2.9k
Un-Boring Meetings
codingconduct
0
460
Navigating Weather and Climate Data
rabernat
0
560
We Have a Design System, Now What?
morganepeng
55
8.4k
New Earth Scene 8
popppiees
4
2.7k
Transcript
re-frame à la spec
Self-introduction /laʒenɔʁɛ̃k/ カマイルカ lagénorhynque (defprofile lagénorhynque :name "Kent OHASHI" :languages
[Clojure Haskell Python Scala English français Deutsch русский] :interests [programming language-learning mathematics] :contributing [github.com/japan-clojurians/clojure-site-ja])
1. Example App 2. Introduction to re-frame 3. Integration with
clojure.spec
Example App rock-paper-scissors game
source code inspired by lagenorhynque/rock-paper-scissors Elm開発における思考フロー
demo or from Emacs: cider-jack-in-clojurescript $ git clone
[email protected]
:lagenorhynque/rock-paper-scissors.git $
cd rock-paper-scissors $ lein figwheel dev
Game start
Select your hand
Result: lose
Select your hand
Result: draw
Select your hand
Result: win
Introduction to re-frame
re-frame A Reagent Framework For Writing SPAs, in Clojurescript.
features based on Reagent data-oriented design purely functional for the
most part
similar frameworks (React) (Angular) Elm Architecture Redux ngrx
React Reagent JSX (React element) Hiccup-like DSL React component ClojureScript
function props args of component function state reagent.core/atom cf. Reagent ClojureScript & ReagentでReact⼊⾨してみた
re-frame's data ow (a.k.a. "dominoes") 1. event dispatch 2. event
handling 3. effect handling 4. query 5. view 6. DOM
1. event dispatch dispatch synchronously to [::events/initialize-db] (defn ^:export init
[] (re-frame/dispatch-sync [::events/initialize-db]) (dev-setup) (mount-root)) src/cljs/rock_paper_scissors/core.cljs#L19-L22
2. event handling event handler for ::initialize-db (re-frame/reg-event-db ::initialize-db (fn
[_ _] db/default-db)) src/cljs/rock_paper_scissors/events.cljs#L6-L9
3. effect handling effect handler for :db (reg-fx :db (fn
[value] (if-not (identical? @app-db value) (reset! app-db value)))) src/re_frame/fx.cljc#L162-L166
4. query subscription for ::scene (re-frame/reg-sub ::scene (fn [db _]
(:scene db))) src/cljs/rock_paper_scissors/subs.cljs#L5-L8
5. view subscribe to [::subs/scene] (defn main-panel [] (case @(re-frame/subscribe
[::subs/scene]) ::db/start [start-button "Game Start"] ::db/now-playing [hands] ::db/over [:div [:h1 @(re-frame/subscribe [::subs/you-enemy-hands])] [result] [start-button "Next Game"]])) src/cljs/rock_paper_scissors/views.cljs#L26-L33
6. DOM
1'. event dispatch dispatch to [::events/next-game] (defn start-button [label] [:input
{:type "button" :on-click #(re-frame/dispatch [::events/next-game]) :value label}]) src/cljs/rock_paper_scissors/views.cljs#L8-L11
2'. event handling event handler for ::next-game (re-frame/reg-event-db ::next-game (fn
[db _] (assoc db :scene ::db/now-playing))) src/cljs/rock_paper_scissors/events.cljs#L11-L14
3'. effect handling effect handler for :db (reg-fx :db (fn
[value] (if-not (identical? @app-db value) (reset! app-db value)))) src/re_frame/fx.cljc#L162-L166
4'. query subscription for ::scene (re-frame/reg-sub ::scene (fn [db _]
(:scene db))) src/cljs/rock_paper_scissors/subs.cljs#L5-L8
5'. view subscribe to [::subs/scene] (defn main-panel [] (case @(re-frame/subscribe
[::subs/scene]) ::db/start [start-button "Game Start"] ::db/now-playing [hands] ::db/over [:div [:h1 @(re-frame/subscribe [::subs/you-enemy-hands])] [result] [start-button "Next Game"]])) src/cljs/rock_paper_scissors/views.cljs#L26-L33
6'. DOM
1''. event dispatch dispatch to [::events/select-your-hand h] (defn hands []
[:div (map (fn [h] ^{:key h} [:input {:type "button" :on-click #(re-frame/dispatch [::events/select-your-hand h]) :value h}]) [::rps/rock ::rps/paper ::rps/scissors])]) src/cljs/rock_paper_scissors/views.cljs#L13-L19
2''. event handling event handler for ::events/select-your-hand (re-frame/reg-event-fx ::select-your-hand [(re-frame/inject-cofx
::cofx/select-enemy-hand)] (fn [{:keys [db enemy-hand]} [_ h]] {:db (assoc db :you h :enemy enemy-hand :scene ::db/over)})) src/cljs/rock_paper_scissors/events.cljs#L16-L23
3''. effect handling coeffect handler for ::select-enemy-hand (re-frame/reg-cofx ::select-enemy-hand (fn
[cofx _] (assoc cofx :enemy-hand (rps/->hand (rand-int 3))))) src/cljs/rock_paper_scissors/cofx.cljs#L5-L9
effect handler for :db (reg-fx :db (fn [value] (if-not (identical?
@app-db value) (reset! app-db value)))) src/re_frame/fx.cljc#L162-L166
4''. query subscription for ::scene (re-frame/reg-sub ::scene (fn [db _]
(:scene db))) src/cljs/rock_paper_scissors/subs.cljs#L5-L8
subscription for ::you-enemy subscription for ::you-enemy-hands (re-frame/reg-sub ::you-enemy (fn [db
_] (select-keys db [:you :enemy]))) src/cljs/rock_paper_scissors/subs.cljs#L10-L13 (re-frame/reg-sub ::you-enemy-hands :<- [::you-enemy] (fn [{:keys [you enemy]} _] (str (name you) "(YOU) VS " (name enemy) "(ENEMY)"))) src/cljs/rock_paper_scissors/subs.cljs#L15-L19
subscription for ::fight-result (re-frame/reg-sub ::fight-result :<- [::you-enemy] (fn [{:keys [you
enemy]} _] (rps/fight you enemy))) src/cljs/rock_paper_scissors/subs.cljs#L21-L25
subscription for ::result-color (re-frame/reg-sub ::result-color :<- [::fight-result] (fn [r _]
(case r ::rps/win "red" ::rps/lose "blue" ::rps/draw "gray"))) src/cljs/rock_paper_scissors/subs.cljs#L27-L34
5''. view subscribe to [::subs/scene] subscribe to [::subs/you-enemy-hands] (defn main-panel
[] (case @(re-frame/subscribe [::subs/scene]) ::db/start [start-button "Game Start"] ::db/now-playing [hands] ::db/over [:div [:h1 @(re-frame/subscribe [::subs/you-enemy-hands])] [result] [start-button "Next Game"]])) src/cljs/rock_paper_scissors/views.cljs#L26-L33
subscribe to [::subs/fight-result] subscribe to [::subs/result-color] (defn result [] (let
[r @(re-frame/subscribe [::subs/fight-result])] [:h1 {:style {:color @(re-frame/subscribe [::subs/result-color])}} r])) src/cljs/rock_paper_scissors/views.cljs#L21-L24
6''. DOM
Integration with clojure.spec
speccing policy split directories/namespaces for specs use clojure.spec in development
only spec domain logic spec db data
split directories/namespaces for specs directory structure rock-paper-scissors ├── specs │
└── cljs │ └── rock_paper_scissors │ └── * │ └── specs.cljs ├── src │ └── cljs │ └── rock_paper_scissors │ └── *.cljs └── test └── cljs └── rock_paper_scissors ├── *_test.cljs └── runner.cljs
use clojure.spec in development only cljsbuild settings :cljsbuild {:builds [{:id
"dev" :source-paths ["src/cljs" "specs/cljs"] ,,,} {:id "min" :source-paths ["src/cljs"] ,,,} {:id "test" :source-paths ["src/cljs" "specs/cljs" "test/cljs"] ,,,} ]} project.clj#L40-L70
spec domain logic specs for rock-paper-scissors data (s/def ::hand #{::rps/rock
::rps/paper ::rps/scissors}) (s/def ::hand-num (s/int-in 0 3)) (s/def ::result #{::rps/win ::rps/lose ::rps/draw}) specs/cljs/rock_paper_scissors/rps/specs.cljs#L5-L9
specs for rock-paper-scissors functions (s/fdef rps/<-hand :args (s/cat :hand ::hand)
:ret ::hand-num) (s/fdef rps/->hand :args (s/cat :num ::hand-num) :ret ::hand) (s/fdef rps/fight :args (s/cat :you ::hand :enemy ::hand) :ret ::result) specs/cljs/rock_paper_scissors/rps/specs.cljs#L11- L22
spec db data (s/def ::you ::rps.specs/hand) (s/def ::enemy ::rps.specs/hand) (s/def
::scene #{::db/start ::db/now-playing ::db/over}) (s/def ::db (s/keys :req-un [::you ::enemy ::scene])) specs/cljs/rock_paper_scissors/db/specs.cljs#L6- L12
testing policy test domain logic test events via dispatch test
subscriptions via subscribe do not test views
test domain logic example-based tests with specs instrumented (t/use-fixtures :once
{:before #(stest/instrument)}) (t/deftest test-fight (t/testing "rock-paper-scissors" (t/is (= ::sut/win (sut/fight ::sut/rock ::sut/scissors))) (t/is (= ::sut/lose (sut/fight ::sut/scissors ::sut/rock))) (t/is (= ::sut/draw (sut/fight ::sut/paper ::sut/paper))))) test/cljs/rock_paper_scissors/rps_test.cljs#L10-L17
property-based tests using specs (tc/defspec prop-test-fight 1000 (let [fspec (s/get-spec
#'sut/fight)] (prop/for-all [[you enemy] (-> fspec :args s/gen)] (s/valid? (:ret fspec) (sut/fight you enemy))))) test/cljs/rock_paper_scissors/rps_test.cljs#L33-L38
test events and subscriptions (t/use-fixtures ; instrument specs :once {:before
#(stest/instrument)}) (defn test-fixtures [] (re-frame/reg-fx ; mock :db effect :db (fn [value] ; validate db with specs (when-not (s/valid? ::db.specs/db value) (throw (ex-info "db spec check failed" (s/explain-data ::db.specs/db value)))) (if-not (identical? @app-db value) (reset! app-db value))))) test/cljs/rock_paper_scissors/events_test.cljs#L16- L26
(t/deftest test-initialize-db (re-frame.test/run-test-sync (test-fixtures) (re-frame/dispatch [::sut/initialize-db]) (t/is ::db/start @(re-frame/subscribe [::subs/scene]))
(t/is {:you ::rps/rock :enemy ::rps/rock} @(re-frame/subscribe [::subs/you-enemy])))) test/cljs/rock_paper_scissors/events_test.cljs#L28- L35
(t/deftest test-select-your-hand (re-frame.test/run-test-sync (test-fixtures) (re-frame/reg-cofx ; mock ::cofx/select-enemy-hand coeffect ::cofx/select-enemy-hand
(fn [cofx _] (assoc cofx :enemy-hand ::rps/rock))) (re-frame/dispatch [::sut/initialize-db]) (t/testing "draw" (re-frame/dispatch [::sut/next-game]) (re-frame/dispatch [::sut/select-your-hand ::rps/rock]) (t/is ::db/over @(re-frame/subscribe [::subs/scene])) (t/is "rock(YOU) VS rock(ENEMY)" @(re-frame/subscribe [::subs/you- (t/is ::rps/draw @(re-frame/subscribe [::subs/fight-result])) (t/is "gray" @(re-frame/subscribe [::subs/result-color]))))) test/cljs/rock_paper_scissors/events_test.cljs#L44- L73
re-frame x clojure.spec unobtrusive use of clojure.spec only in development
in separate directory/file/namespace focus on domain logic and db
A little effort to write specs and tests can make
our ClojureScript frontend life much happier!
Further Reading example code lagenorhynque/rock-paper-scissors
Reagent : A minimalistic ClojureScript interface to React.js reagent-project/reagent Guide
to Reagent ClojureScript & ReagentでReact⼊⾨してみた - Qiita
re-frame : A Reagent Framework For Writing SPAs, in Clojurescript.
: Cross platform (cljs and clj) utilities for testing re-frame applications : A debugging dashboard for re-frame epochs. Comes with free x-ray glasses. Day8/re-frame Day8/re-frame-test Day8/re-frame-10x Re-frame: The Guide to Building Blocks
clojure.spec clojure.spec - Rationale and Overview clojure.spec - 論理的根拠と概要 spec
Guide やってみる!clojure.spec Spectacular Future with clojure.spec