Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
やってみる!clojure.spec
Search
OHTA Shogo
March 14, 2018
Programming
1.1k
4
Share
やってみる!clojure.spec
2018/3/14のclj-nakano #5の資料です。
OHTA Shogo
March 14, 2018
More Decks by OHTA Shogo
See All by OHTA Shogo
テンクーでのClojure活用事例
athos
0
390
軽量デバッグツールPostmortemの紹介.pdf
athos
1
210
Clojure 1.10 概要紹介
athos
3
680
kitchen-async: a promising (?) Promise library, or a poor man's core.async
athos
3
500
Clojure 1.9 概要紹介
athos
4
1.5k
ここ最近のClojureScript
athos
5
1.8k
(= ? (+ nREPL Docker))
athos
0
580
clojure.specの話
athos
3
2.4k
clojure.specの話(仮)
athos
2
370
Other Decks in Programming
See All in Programming
L’IA au service des devs : Anatomie d'un assistant de Code Review
toham
0
190
最初からAWS CDKで技術検証してもいいんじゃない?
akihisaikeda
4
180
Kubernetes上でAgentを動かすための最新動向と押さえるべき概念まとめ
sotamaki0421
2
380
ローカルで稼働するAI エージェントを超えて / beyond-local-ai-agents
gawa
1
240
Strategy for Finding a Problem for OSS: With Real Examples
kibitan
0
130
Codex CLI でつくる、Issue から merge までの開発フロー
amata1219
0
300
PHPで TLSのプロトコルを実装してみる
higaki_program
0
730
Feature Toggle は捨てやすく使おう
gennei
0
410
Goの型安全性で実現する複数プロダクトの権限管理
ishikawa_pro
2
1.4k
一度始めたらやめられない開発効率向上術 / Findy あなたのdotfilesを教えて!
k0kubun
4
2.8k
見せてもらおうか、 OpenSearchの性能とやらを!
shunta27
1
170
「速くなった気がする」をデータで疑う
senleaf24
0
140
Featured
See All Featured
Mobile First: as difficult as doing things right
swwweet
225
10k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
64
53k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
21k
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.4k
Building Flexible Design Systems
yeseniaperezcruz
330
40k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.8k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
160
Ethics towards AI in product and experience design
skipperchong
2
250
Balancing Empowerment & Direction
lara
5
1k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
10k
Prompt Engineering for Job Search
mfonobong
0
250
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8k
Transcript
ͬͯΈΔʂDMPKVSFTQFD DMKOBLBOP !BUIPT
ࣗݾհ ‣ 5XJUUFS!BUIPT ‣ (JU)VCBUIPT ‣ χϟϯύεגࣜձࣾॴଐ ‣ $MPKVSFίϯτϦϏϡʔλ
ࠓͷ༰ ‣ DMPKVSFTQFDೖ എܠ DMPKVSFTQFDͱ TQFDͷجຊతͳ͍ํ ‣
DMPKVSFTQFDΛͬͯΈΔ ؆୯ͳαϯϓϧϓϩδΣΫτͷதͰͬͯΈ·͠ΐ͏
DMPKVSFTQFDೖ
എܠɿ$MPKVSFͷ՝ ‣ ؔΛͲ͏ͬͯ͏ͷ͔͔Γʹ͍͘ʜ ҾʹԿΛड͚औΔͷ͔ɺΓͱͯ͠ԿΛฦ͢ͷ͔ $MPKVSFͰͨͩͷϚοϓϕΫλΛଟ༻͢ΔͷͰɺɹ Ͳ͏͍͏͕ظ͞Ε͍ͯΔͷ͔ผ͠ʹ͍͘ ‣ Τϥʔϝοηʔδ͕͔Γʹ͍͘ʜ
$MPKVSFͷؔجຊతʹ(BSCBHF*O(BSCBHF0VU ޡͬͨೖྗʹରͯ͠ɺ+7.ͷΤϥʔ͕ͦͷ··ग़Δ ͜ͱଟ͍
DMPKVSFTQFDͱ ‣ $MPKVSFͰಋೖ͞Εͨ৽ػೳ ‣ ड़ޠϕʔεͰσʔλܕؔͷ༷Λهड़͠ɺͦΕΛνΣοΫ͢ΔΈ ੩తݕࠪͰͳ͘ɺܖϓϩάϥϛϯάʹ͍ۙ ‣ Ұ༷ εϖοΫ
Λॻ͚։ൃͷ༷ʑͳ໘Ͱར༻Մ υΩϡϝϯςʔγϣϯ όϦσʔγϣϯ σʔλੜ ϓϩύςΟϕʔεςετ ϚΫϩͷߏจνΣοΫ
s/valid? ‣ (s/valid? <εϖοΫ> <>) ͕εϖοΫΛຬ͍ͨͯ͠Δ͔ΛνΣοΫ͢Δ (require ‘[clojure.spec.alpha :as
s]) (s/valid? int? 42) ;; => true (s/valid? int? :foo) ;; => false
ड़ޠεϖοΫʹͳΔ ‣ ҙͷड़ޠ ਅِΛฦؔ͢ ΛεϖοΫͱͯ͑͠Δ ‣ ࠐΈ͚ؔͩͰͳ͘ϢʔβఆٛؔͰແ໊ؔͰՄ (s/valid? string? “foo”)
;; => true (s/valid? #(> % 10) 11) ;; => true
εϖοΫʹ໊લΛ͚ͭΔ ‣ s/defͰεϖοΫʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ ‣ εϖοΫΛ࠶ར༻Ͱ͖Δ (s/def ::id int?) (s/def :person/name
string?) (s/valid? ::id 42) ;; => true (s/valid? :person/name “Rich Hickey”) ;; => true
ू߹εϖοΫ ‣ ू߹#{e1 … en}e1 ʜ en ͷ͍ͣΕ͔ͱ͍͠Α͏ͳ Λද͢εϖοΫ ‣
ڞ༻ମͷΑ͏ͳΛදݱ͢Δͷʹศར (s/valid? #{0 1 2} 3) ;; => false (s/valid? #{0 1 2} 2) ;; => true
ίϨΫγϣϯͷεϖοΫ ‣ s/coll-of s/map-ofͰίϨΫγϣϯͱͦͷཁૉ ͷεϖοΫΛද͢͜ͱ͕Ͱ͖Δ (s/def ::nums (s/coll-of int?))
(s/def ::vals (s/map-of keyword? int?)) (s/valid? ::nums [3 1 4]) ;; => true (s/valid? ::vals {:a 0 :b 1}) ;; => true
ϚοϓͷεϖοΫ ‣ s/keysͰΩʔʹΑΓͷܕ͕ҧ͏ϚοϓΛදݱͰ͖Δ ‣ :req-unͰඞਢͷΩʔɺ:opt-unͰΦϓγϣφϧͳΩʔΛࢦఆ ͢Δ (s/def ::id int?) (s/def
::name string?) (s/def ::gender #{:male :female}) (s/def ::person (s/keys :req-un [::id ::name] :opt-un [::gender]) (s/valid? ::person {:id 1 :name “athos” :gender :male}) ;; => true (s/valid? ::person {:id 1 :name “athos”}) ;; => true
s/explain ‣ (s/explain <εϖοΫ> <>) ͕εϖοΫΛຬ͍ͨͯ͠ͳ͍߹ʹɺͲ͕͜Ͳ͏ޡͬͯ ͍Δ͔Λࢦఠͯ͘͠ΕΔ ग़ྗܗࣜΧελϚΠζͰ͖Δ
(s/def ::id int?) (s/def ::name string?) (s/def ::person (s/keys :req-un [::id ::name])) (s/explain ::person {:id “42”}) ;; In: [:id] val: "42" fails spec: :user/id at: [:id] predicate: int? ;; val: {:id "42"} fails spec: :user/person predicate: (contains? % :name) ˡ:idint?ͩͱݴ͍ͬͯΔ ˡ:name͕ͳ͍ͱݴ͍ͬͯΔ
s/gen ‣ (s/gen <εϖοΫ>) εϖοΫΛຬͨ͢ϥϯμϜͳΛੜ͢ΔδΣωϨʔλΛ ฦ͢ UFTUDIFDLHFOFSBUPSTΛͬͯϥϯμϜͳΛੜͰ͖Δ (require
‘[clojure.test.check.generators :as gen]) (s/gen (s/coll-of int?)) ;; => δΣωϨʔλ (gen/generate (s/gen (s/coll-of int?))) ;; => [-163 -1669986 346763815 11309055] (gen/generate (s/gen (s/coll-of int?))) ;; => [-116969371 -262061 95 10599611 36137714]
ؔͷεϖοΫ ‣ ؔͷҾ͓ΑͼΓͷεϖοΫΛهड़Ͱ͖Δ ‣ :fnͰҾͱΓͷؒͷؔʹ͍ͭͯنఆͰ͖Δ (defn square [x] (* x
x)) (s/fdef square :args (s/cat :x int?) :ret int?)
st/instrument ‣ st/instrumentͰؔͷҾ͕εϖοΫΛຬ͍ͨͯ͠Δ͔ ͷνΣοΫ͕༗ޮʹͳΔ ‣ ։ൃ࣌ɾςετ࣌ʹ༗ޮʹ͓ͯ͘͠ͱศར (require ‘[clojure.spec.test.alpha :as st])
(st/instrument `square) (square 3) ;; => 9 (square :foo) ;; ExceptionInfo Call to #'user/square did not conform to spec: ;; In: [0] val: :foo fails at: [:args :x] predicate: int? ;; clojure.core/ex-info (core.clj:4739)
st/check ‣ ҾͷεϖοΫ͔ΒϥϯμϜͳೖྗΛ࡞ͬͯؔʹ͠ɺ Γ͕ΓͷεϖοΫΛຬ͔ͨ͢Ͳ͏͔ΛνΣοΫ (st/check `square) ;; ({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451 0x7caa6a11
"clojure.spec.alpha$fspec_impl$reify__2451@7caa6a11" ], :clojure.spec.test.check/ret {:result #error { :cause "integer overflow" :via [{:type java.lang.ArithmeticException :message "integer overflow" :at [clojure.lang.Numbers throwIntOverflow "Numbers.java" 1526]}] …
ࢀߟɿsquareͷగਖ਼൛ ‣ *MPOHͷൣғͷಉ࢜ͷֻ͚ࢉΦʔόʔϑϩʔ͢Δ Մೳੑ͕͋ΔͷͰΘΓʹ*’Λ͏ ‣ #JH*OUJOU Ͱͳ͍ͷͰJOUFHFS Λ͏ (defn square
[x] (*’ x x)) (s/fdef square :args (s/cat :x int?) :ret integer?)
DMPKVSFTQFDΛͬͯΈΔ
՝ɿλεΫཧ ‣ ΠϯϝϞϦͰ3&1-͔Β͏λεΫཧ ‣ IUUQTHJUIVCDPNBUIPTTQFDFYBNQMF ‣ ༷ λεΫ*%ͱઆ໌ EFTDSJQUJPO
ɺঢ়ଶ TUBUVT Λ࣋ͭ λεΫϦετݸҎ্ͷλεΫΛอ࣋Ͱ͖Δ λεΫϦετʹ*%Λࢦఆͯ͠λεΫΛऔಘͨ͠ΓɺλεΫͷઆ ໌ঢ়ଶΛมߋͨ͠ΓͰ͖Δ λεΫͷঢ়ଶQFOEJOH͔EPOFͷ͍ͣΕ͔ ॳظQFOEJOH
͡Ί͔ͨ ‣ 3&1-Λىಈ ‣ ͘͠ ‣ 3&1-ͰҎԼΛೖྗ $ clj -Adev
$ lein repl (goto ‘spec-example.todo)
λεΫͷεϖοΫ (ns spec-example.todo (:require [clojure.spec.alpha :as s])) (s/def ::id nat-int?)
(s/def ::description string?) (s/def ::status #{:pending :done}) (s/def ::task (s/keys :req-un [::id ::description ::status])) (s/def ::items (s/map-of ::id ::task)) (s/def ::task-list (s/keys :req-un [::items])) (def empty-task-list {:items {}})
λεΫΛੜͯ͠ΈΔ (require ‘[clojure.test.check.generators :as gen]) (gen/generate (s/gen ::task)) ;; =>
{:id 19861176, :description “bVRFEN89TC", :status :done} (gen/generate (s/gen ::task)) ;; => {:id 16, :description “n18x5AwDH0Ej0yyvyw1u8FLv", :status :done}
λεΫϦετΛੜͯ͠ΈΔ (require ‘[clojure.test.check.generators :as gen]) (gen/generate (s/gen ::task-list)) ;; =>
{:items {87201 {:id 1, :description "8J6aNkRHe1dPSc8mtp0AMKKg4", :status :pending}, 58 {:id 196, :description "juPASwWa55URSp21", :status :done}, 47466 {:id 3, :description "F3O9", :status :pending}, 5 {:id 37, :description "kTN7H180rUcqHg9sZ29699mJu14c", :status :done}, 61 {:id 2529932, :description "eLO00", :status :done}, 811 {:id 206, :description "3gyMilIqz0aw", :status :pending}, 35 {:id 28457067, :description "RRxYbrL38Uj42kR1MiJ2S8qMZ2t", :status :pending}}}
add-task (defn add-task [tasks description] (let [id (count (:items tasks))
task {:id id :description description :status :pending}] (assoc-in tasks [:items id] task))) (s/fdef add-task :args (s/cat :tasks ::task-list :description ::description) :ret ::task-list)
add-taskΛinstrumentͯ͠ΈΔ (require ‘[clojure.spec.test.alpha :as st]) (st/instrument `add-task) (add-task empty-task-list “buy
milk”) ;; => {:items {0 {:id 0, :description “buy milk”, :status :pending}}} (add-task empty-task-list :buy-milk) ;; ExceptionInfo Call to #'spec-example.todo/add-task did not conform to spec: ;; In: [1] val: :buy-milk fails spec: :spec-example.todo/description at: [:args :description] predicate: string? ;; clojure.core/ex-info (core.clj:4739)
add-taskΛcheckͯ͠ΈΔ (st/check `add-task) ;; => ({:spec #object[clojure.spec.alpha$fspec_impl$reify __2451 0x5e62054e “clojure.spec.alpha$fspec_impl$reify__2451@
5e62054e"], :clojure.spec.test.check/ret {:result true, :num-tests 1000, :seed 1521012250499}, :sym spec-example.todo/add-task})
checkΛ௨ͯ͠ΈΔ ‣ count-tasksͱall-tasksʹਖ਼͍͠εϖοΫΛఆٛ͠ ͯɺcheckΛ௨ͯ͠Έ·͠ΐ͏ (defn count-tasks [tasks] (count (:items tasks)))
(defn all-tasks [tasks] (sequence (vals (:items tasks))))
checkΛ௨ͯ͠ΈΔ ‣ count-tasksͱall-tasksʹਖ਼͍͠εϖοΫΛఆٛ͠ ͯɺcheckΛ௨ͯ͠Έ·͠ΐ͏ (s/fdef count-tasks :args (s/cat :tasks ::task-list)
:ret int?) (s/fdef all-tasks :args (s/cat :tasks ::task-list) :ret (s/coll-of ::task))
͕࣌ؒ͋Ε ϓϩύςΟϕʔεςετͷհ
·ͱΊ ‣ DMPKVSFTQFDͰσʔλؔͷεϖοΫΛఆٛ͢Δ͜ͱ Ͱɺ$MPKVSFͰΑΓݎ࿚ͳίʔυΛॻ͚ΔΑ͏ʹ ‣ ҰεϖοΫΛఆ͓ٛͯ͘͠ͱɺ։ൃ࣌ɾςετ࣌ͳͲ ༷ʑͳ໘ͰεϖοΫΛԠ༻͢Δ͜ͱ͕Ͱ͖Δ ‣ ͨͩ͠ɺ৽͔ͭ͘͠ಠಛͷػೳͳͨΊɺ·ͩ·ͩϕετ ϓϥΫςΟεݻ·͍ͬͯͳ͍෦ଟ͍
‣ ΈΜͳͰҰॹʹ͍͖ͬͯ·͠ΐ͏
͓͢͢ΊϥΠϒϥϦ ‣ 0SDIFTUSBinstrumentͰΓνΣοΫͯ͘͠ΕΔ IUUQTHJUIVCDPNKFBZFPSDIFTUSB ‣ UFTUDIVDLUFTUDIFDLΛ͏ͱ͖ͷϢʔςΟϦςΟू IUUQTHJUIVCDPNHGSFEFSJDLTUFTUDIVDL ‣ &YQPVOEFYQMBJOͷ݁ՌΛݟ͘͢ܗͯ͘͠ΕΔ IUUQTHJUIVCDPNCICFYQPVOE
‣ 1JOQPJOUFSಉ্ IUUQTHJUIVCDPNBUIPT1JOQPJOUFS
ϦϯΫू ‣ DMPKVSFTQFDͷ֓ཁ ຊޠʂ IUUQTKBQBODMPKVSJBOTHJUIVCJPDMPKVSFTJUFKBBCPVUTQFDIUNM ‣ DMPKVSFTQFDΨΠυ IUUQTDMPKVSFPSHHVJEFTTQFD ‣
1SPHSBNNJOH$MPKVSF SEFE IUUQTQSBHQSPHDPNCPPLTIDMPKQSPHSBNNJOHDMPKVSFUIJSEFEJUJPO