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
4
1k
やってみる!clojure.spec
2018/3/14のclj-nakano #5の資料です。
OHTA Shogo
March 14, 2018
Tweet
Share
More Decks by OHTA Shogo
See All by OHTA Shogo
テンクーでのClojure活用事例
athos
0
350
軽量デバッグツールPostmortemの紹介.pdf
athos
1
200
Clojure 1.10 概要紹介
athos
3
650
kitchen-async: a promising (?) Promise library, or a poor man's core.async
athos
3
480
Clojure 1.9 概要紹介
athos
4
1.5k
ここ最近のClojureScript
athos
5
1.7k
(= ? (+ nREPL Docker))
athos
0
540
clojure.specの話
athos
3
2.3k
clojure.specの話(仮)
athos
2
360
Other Decks in Programming
See All in Programming
TDD 実践ミニトーク
contour_gara
1
260
Updates on MLS on Ruby (and maybe more)
sylph01
1
170
ソフトウェアテスト徹底指南書の紹介
goyoki
1
120
コンテキストエンジニアリング Cursor編
kinopeee
1
730
Zendeskのチケットを Amazon Bedrockで 解析した
ryokosuge
3
230
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
270
TROCCO×dbtで実現する人にもAIにもやさしいデータ基盤
nealle
0
390
為你自己學 Python - 冷知識篇
eddie
1
310
詳解!defer panic recover のしくみ / Understanding defer, panic, and recover
convto
0
210
実用的なGOCACHEPROG実装をするために / golang.tokyo #40
mazrean
1
120
モバイルアプリからWebへの横展開を加速した話_Claude_Code_実践術.pdf
kazuyasakamoto
0
290
複雑なドメインに挑む.pdf
yukisakai1225
4
810
Featured
See All Featured
Producing Creativity
orderedlist
PRO
347
40k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Writing Fast Ruby
sferik
628
62k
Rebuilding a faster, lazier Slack
samanthasiow
83
9.1k
Stop Working from a Prison Cell
hatefulcrawdad
271
21k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
18
1.1k
Facilitating Awesome Meetings
lara
55
6.5k
Fireside Chat
paigeccino
39
3.6k
Keith and Marios Guide to Fast Websites
keithpitt
411
22k
Done Done
chrislema
185
16k
jQuery: Nuts, Bolts and Bling
dougneiner
64
7.9k
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