2016/07/29 Lisp meetup #42 の発表資料です。
DMPKVSFTQFDͷ-JTQNFFUVQ!BUIPT
View Slide
ࣗݾհ‣ 5XJUUFS!BUIPT‣ χϟϯύεגࣜձࣾॴଐ‣ $MPKVSFίϯτϦϏϡʔλ
DMPKVSFTQFD‣ ࣍ظϦϦʔεͰͷಋೖ͕ਐΊΒΕ͍ͯΔ৽ػೳ‣ ੩తܕͰͳ͘ɺड़ޠͷΈ߹ΘͤʹΑͬͯσʔλܕɾؔͷ༷Λهड़͢Δ‣ Ұ༷ εϖοΫΛॻ͚ҰཻͰԿ͓͍͍͠ υΩϡϝϯςʔγϣϯ ܖϓϩάϥϛϯά ϓϩύςΟϕʔεςετ ϚΫϩͷߏจνΣοΫ
DMPKVSFTQFDͷ͍ํ‣ [org.clojure/clojure “1.9.0-alphaXX”]Λ:dependenciesʹՃ‣ ࠷৽൛BMQIB ݱࡏ‣ (require ‘[clojure.spec :as s])
όϦσʔλͱͯ͠ͷDMPKVSFTQFD
ड़ޠ‣ ड़ޠ CPPMΛฦؔͦ͢ͷ··ͰεϖοΫͱͯ͑͠Δuser=> (s/valid? integer? 42)
ड़ޠ‣ ड़ޠ CPPMΛฦؔͦ͢ͷ··ͰεϖοΫͱͯ͑͠Δuser=> (s/valid? integer? 42)trueuser=> (s/valid? integer? “foo”)
ड़ޠ‣ ड़ޠ CPPMΛฦؔͦ͢ͷ··ͰεϖοΫͱͯ͑͠Δuser=> (s/valid? integer? 42)trueuser=> (s/valid? integer? “foo”)falseuser=>
WBMJE FYQMBJO‣ valid?εϖοΫΛຬ͔ͨ͢Ͳ͏͔Λฦ͢‣ explainεϖοΫΛຬͨ͞ͳ͍ՕॴΛࢦఠ͢Δuser=> (s/valid? integer? “foo”)falseuser=> (s/explain integer? “foo”)
WBMJE FYQMBJO‣ valid?εϖοΫΛຬ͔ͨ͢Ͳ͏͔Λฦ͢‣ explainεϖοΫΛຬͨ͞ͳ͍ՕॴΛࢦఠ͢Δuser=> (s/valid? integer? “foo”)falseuser=> (s/explain integer? “foo”)val: "foo" fails predicate: :clojure.spec/unknownniluser=> (s/explain integer? 42)
WBMJE FYQMBJO‣ valid?εϖοΫΛຬ͔ͨ͢Ͳ͏͔Λฦ͢‣ explainεϖοΫΛຬͨ͞ͳ͍ՕॴΛࢦఠ͢Δuser=> (s/valid? integer? “foo”)falseuser=> (s/explain integer? “foo”)val: "foo" fails predicate: :clojure.spec/unknownniluser=> (s/explain integer? 42)Success!niluser=>
εϖοΫͷ߹‣ andorΛͬͯෳͷεϖοΫΛΈ߹ΘͤՄೳuser=> (s/valid? (s/and integer? even?) 0)trueuser=> (s/valid? (s/and integer? even?) 1)false
εϖοΫͷ߹‣ andorΛͬͯෳͷεϖοΫΛΈ߹ΘͤՄೳuser=> (s/valid? (s/and integer? even?) 0)trueuser=> (s/valid? (s/and integer? even?) 1)falseuser=> (s/valid? (s/or :int integer?:str string?)“foo”)
εϖοΫͷ߹‣ andorΛͬͯෳͷεϖοΫΛΈ߹ΘͤՄೳuser=> (s/valid? (s/and integer? even?) 0)trueuser=> (s/valid? (s/and integer? even?) 1)falseuser=> (s/valid? (s/or :int integer?:str string?)“foo”)trueuser=>
εϖοΫʹ໊લΛ͚ͭΔ‣ defͰεϖοΫʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ‣ εϖοΫࣗମΛ࠶ར༻Ͱ͖Δuser=> (s/def ::answer-to-everything(fn [x] (= x 42)):user/answer-to-everythinguser=> (s/valid? ::answer-to-everything 43)
εϖοΫʹ໊લΛ͚ͭΔ‣ defͰεϖοΫʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ‣ εϖοΫࣗମΛ࠶ར༻Ͱ͖Δuser=> (s/def ::answer-to-everything(fn [x] (= x 42)):user/answer-to-everythinguser=> (s/valid? ::answer-to-everything 43)falseuser=> (s/valid? ::answer-to-everything 42)
εϖοΫʹ໊લΛ͚ͭΔ‣ defͰεϖοΫʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ‣ εϖοΫࣗମΛ࠶ར༻Ͱ͖Δuser=> (s/def ::answer-to-everything(fn [x] (= x 42)):user/answer-to-everythinguser=> (s/valid? ::answer-to-everything 43)falseuser=> (s/valid? ::answer-to-everything 42)trueuser=>
ίϨΫγϣϯͷεϖοΫ‣ coll-ofmap-ofͰίϨΫγϣϯͷཁૉ͕εϖοΫΛຬ͍ͨͯ͠Δ͔νΣοΫͰ͖Δuser=> (s/valid? (s/coll-of integer?) [1 2 3])
ίϨΫγϣϯͷεϖοΫ‣ coll-ofmap-ofͰίϨΫγϣϯͷཁૉ͕εϖοΫΛຬ͍ͨͯ͠Δ͔νΣοΫͰ͖Δuser=> (s/valid? (s/coll-of integer?) [1 2 3])trueuser=> (s/valid? (s/coll-of integer?) [1 :a])
ίϨΫγϣϯͷεϖοΫ‣ coll-ofmap-ofͰίϨΫγϣϯͷཁૉ͕εϖοΫΛຬ͍ͨͯ͠Δ͔νΣοΫͰ͖Δuser=> (s/valid? (s/coll-of integer?) [1 2 3])trueuser=> (s/valid? (s/coll-of integer?) [1 :a])falseuser=> (s/valid? (s/map-of keyword? integer?){:a 0, :b 1})
ίϨΫγϣϯͷεϖοΫ‣ coll-ofmap-ofͰίϨΫγϣϯͷཁૉ͕εϖοΫΛຬ͍ͨͯ͠Δ͔νΣοΫͰ͖Δuser=> (s/valid? (s/coll-of integer?) [1 2 3])trueuser=> (s/valid? (s/coll-of integer?) [1 :a])falseuser=> (s/valid? (s/map-of keyword? integer?){:a 0, :b 1})trueuser=> (s/valid? (s/map-of keyword? integer?){:a 0, :b “foo”})
ίϨΫγϣϯͷεϖοΫ‣ coll-ofmap-ofͰίϨΫγϣϯͷཁૉ͕εϖοΫΛຬ͍ͨͯ͠Δ͔νΣοΫͰ͖Δuser=> (s/valid? (s/coll-of integer?) [1 2 3])trueuser=> (s/valid? (s/coll-of integer?) [1 :a])falseuser=> (s/valid? (s/map-of keyword? integer?){:a 0, :b 1})trueuser=> (s/valid? (s/map-of keyword? integer?){:a 0, :b “foo”})falseuser=>
ίϨΫγϣϯͷεϖοΫ‣ ΩʔʹΑͬͯͷܕ͕ҧ͏ϚοϓͷεϖοΫఆٛՄೳuser=> (s/def ::x integer?):user/xuser=> (s/def ::y string?):user/yuser=> (s/explain (s/keys :req-un [::x ::y]){:x 1, :y 2})
ίϨΫγϣϯͷεϖοΫ‣ ΩʔʹΑͬͯͷܕ͕ҧ͏ϚοϓͷεϖοΫఆٛՄೳuser=> (s/def ::x integer?):user/xuser=> (s/def ::y string?):user/yuser=> (s/explain (s/keys :req-un [::x ::y]){:x 1, :y 2})In: [:y] val: 2 fails spec: :user/y at: [:y]predicate: string?niluser=> (s/valid? (s/keys :req-un [::x ::y]){:x 1, :y “foo”})
ίϨΫγϣϯͷεϖοΫ‣ ΩʔʹΑͬͯͷܕ͕ҧ͏ϚοϓͷεϖοΫఆٛՄೳuser=> (s/def ::x integer?):user/xuser=> (s/def ::y string?):user/yuser=> (s/explain (s/keys :req-un [::x ::y]){:x 1, :y 2})In: [:y] val: 2 fails spec: :user/y at: [:y]predicate: string?niluser=> (s/valid? (s/keys :req-un [::x ::y]){:x 1, :y “foo”})trueuser=>
%C$πʔϧͱͯ͠ͷDMPKVSFTQFD
ؔͷεϖοΫ‣ fizzbuzzؔͷεϖοΫ্ͷΑ͏ʹఆٛͰ͖Δ‣ :args :ret͕ͦΕͧΕࣄલ݅ɾࣄޙ݅ʹରԠ(s/fdef fizzbuzz:args (s/cat :n (s/and integer? #(> % 0))):ret (s/or :int integer? :key keyword?))(defn fizzbuzz [n](cond (= (mod n 15) 0) :fizzbuzz(= (mod n 5) 0) :buzz(= (mod n 3) 0) “fizz” ;;←όά:else n))
JOTUSVNFOU‣ ؔʹҾ͕εϖοΫΛຬ͍ͨͯ͠Δ͔ͷνΣοΫΛΦϯɾΦϑͰ͖Δuser=> (require ’[clojure.spec.test :as t])niluser=> (t/instrument)
JOTUSVNFOU‣ ؔʹҾ͕εϖοΫΛຬ͍ͨͯ͠Δ͔ͷνΣοΫΛΦϯɾΦϑͰ͖Δuser=> (require ’[clojure.spec.test :as t])niluser=> (t/instrument)[user/fizzbuzz]user=> (fizzbuzz 15)
JOTUSVNFOU‣ ؔʹҾ͕εϖοΫΛຬ͍ͨͯ͠Δ͔ͷνΣοΫΛΦϯɾΦϑͰ͖Δuser=> (require ’[clojure.spec.test :as t])niluser=> (t/instrument)[user/fizzbuzz]user=> (fizzbuzz 15)“fizzbuzz”user=> (fizzbuzz “foo”)
JOTUSVNFOU‣ ؔʹҾ͕εϖοΫΛຬ͍ͨͯ͠Δ͔ͷνΣοΫΛΦϯɾΦϑͰ͖Δuser=> (require ’[clojure.spec.test :as t])niluser=> (t/instrument)[user/fizzbuzz]user=> (fizzbuzz 15)“fizzbuzz”user=> (fizzbuzz “foo”)ExceptionInfo Call to #'user/fizzbuzz did not conform tospec:In: [0] val: "foo" fails at: [:args :n] predicate: integer?:clojure.spec/args ("foo") …user=>
DIFDL‣ ҾͷεϖοΫΛຬͨ͢Λࣗಈੜͯ͠ɺͦͷΛؔʹͨ݁͠Ռ͕ΓͷεϖοΫΛຬ͔ͨ͢νΣοΫ͢Δuser=> (t/check)
DIFDL‣ ҾͷεϖοΫΛຬͨ͢Λࣗಈੜͯ͠ɺͦͷΛؔʹͨ݁͠Ռ͕ΓͷεϖοΫΛຬ͔ͨ͢νΣοΫ͢Δuser=> (t/check)({:spec …:clojure.spec.test.check/ret{:result#error {:cause "Specification-based check failed”:data {:clojure.spec/problems(… {:path [:ret :key],:pred keyword?,:val “fizz",:via [], :in [],:clojure.spec.test/args (3),:clojure.spec/failure :check-failed}…)}user=>
DIFDL‣ ҾͷεϖοΫΛຬͨ͢Λࣗಈੜͯ͠ɺͦͷΛؔʹͨ݁͠Ռ͕ΓͷεϖοΫΛຬ͔ͨ͢νΣοΫ͢Δuser=> (t/check)({:spec …:clojure.spec.test.check/ret{:result#error {:cause "Specification-based check failed”:data {:clojure.spec/problems(… {:path [:ret :key],:pred keyword?,:val “fizz",:via [], :in [],:clojure.spec.test/args (3),:clojure.spec/failure :check-failed}…)}user=>Ͱݺͼग़ͨ͠ͱ͖ʹࣦഊ͢Δ͜ͱΛݕग़
͕࣌ؒ͋Γͦ͏ͳΒԠ༻ྫΛհ
δΣωϨʔλͱͯ͠ͷDMPKVSFTQFD
HFO‣ εϖοΫ͔ΒUFTUDIFDL༻ͷδΣωϨʔλΛ࡞Δ‣ εϖοΫΛຬͨ͢ϥϯμϜͳΛੜͰ͖Δ‣ 3&1-Ͱαϯϓϧσʔλ͕΄͍͠ͱ͖ʹศརuser=> (require ’[clojure.test.check.generators :asgen])niluser=> (gen/generate (s/gen (s/coll-of integer?)))
HFO‣ εϖοΫ͔ΒUFTUDIFDL༻ͷδΣωϨʔλΛ࡞Δ‣ εϖοΫΛຬͨ͢ϥϯμϜͳΛੜͰ͖Δ‣ 3&1-Ͱαϯϓϧσʔλ͕΄͍͠ͱ͖ʹศརuser=> (require ’[clojure.test.check.generators :asgen])niluser=> (gen/generate (s/gen (s/coll-of integer?)))[16719156 -26693 47]user=> (gen/generate (s/gen (s/coll-of integer?)))
HFO‣ εϖοΫ͔ΒUFTUDIFDL༻ͷδΣωϨʔλΛ࡞Δ‣ εϖοΫΛຬͨ͢ϥϯμϜͳΛੜͰ͖Δ‣ 3&1-Ͱαϯϓϧσʔλ͕΄͍͠ͱ͖ʹศརuser=> (require ’[clojure.test.check.generators :asgen])niluser=> (gen/generate (s/gen (s/coll-of integer?)))[16719156 -26693 47]user=> (gen/generate (s/gen (s/coll-of integer?)))[-158637744 -8 -461005 -238354 59127 -4365]user=>
HFOΛͬͨϓϩύςΟϕʔεςετ‣ εϖοΫΛຬͨ͢Λࣗಈੜ͠ɺͯ͢ͷʹରͯ͠ੑ࣭͕Γཱ͔ͭͲ͏͔νΣοΫ͢Δ(ns fizzbuzz-test(:require [clojure.test.check.clojure-test :refer [defspec]][clojure.test.check.properties :as prop][clojure.spec :as s][fizzbuzz :as fb]))(defspec fizzbuzz-prop(prop/for-all [n (s/gen (s/and integer? #(> % 0)))](let [v (fb/fizzbuzz n)](cond (= (mod n 3) 0) (contains? #{:fizz :fizzbuzz} v)(= (mod n 5) 0) (contains? #{:buzz :fizzbuzz} v):else (= n v)))))
ςετ࣮ߦ݁Ռ$ lein test
ςετ࣮ߦ݁Ռ$ lein testlein test fizzbuzz-test{:result false, :seed 1469711295643, :failing-size 4, :num-tests 5, :fail [96], :shrunk {:total-nodes-visited 6, :depth5, :result false, :smallest [3]}, :test-var "fizzbuzz-prop"}lein test :only fizzbuzz-test/fizzbuzz-propFAIL in (fizzbuzz-prop) (clojure_test.cljc:21)expected: resultactual: falseRan 1 tests containing 1 assertions.1 failures, 0 errors.Tests failed.$
ύʔαͱͯ͠ͷDMPKVSFTQFD
γʔέϯεͷεϖοΫ SFHFY‣ SFHFYͰཁૉͷฒͼʹରͯ͠εϖοΫΛఆٛͰ͖Δuser=> (s/valid? (s/* integer?) ’(1 2 3))trueuser=> (s/valid? (s/alt :i integer? :s string?)’(“foo”))
γʔέϯεͷεϖοΫ SFHFY‣ SFHFYͰཁૉͷฒͼʹରͯ͠εϖοΫΛఆٛͰ͖Δuser=> (s/valid? (s/* integer?) ’(1 2 3))trueuser=> (s/valid? (s/alt :i integer? :s string?)’(“foo”))trueuser=> (s/valid? (s/cat :i integer? :s string?)’(1 “foo”))
γʔέϯεͷεϖοΫ SFHFY‣ SFHFYͰཁૉͷฒͼʹରͯ͠εϖοΫΛఆٛͰ͖Δuser=> (s/valid? (s/* integer?) ’(1 2 3))trueuser=> (s/valid? (s/alt :i integer? :s string?)’(“foo”))trueuser=> (s/valid? (s/cat :i integer? :s string?)’(1 “foo”))trueuser=> (s/valid? (s/cat :i* (s/* integer?):s* (s/* string?))’(1 2 3 “foo” “bar”))
γʔέϯεͷεϖοΫ SFHFY‣ SFHFYͰཁૉͷฒͼʹରͯ͠εϖοΫΛఆٛͰ͖Δuser=> (s/valid? (s/* integer?) ’(1 2 3))trueuser=> (s/valid? (s/alt :i integer? :s string?)’(“foo”))trueuser=> (s/valid? (s/cat :i integer? :s string?)’(1 “foo”))trueuser=> (s/valid? (s/cat :i* (s/* integer?):s* (s/* string?))’(1 2 3 “foo” “bar”))trueuser=>
DPOGPSN‣ εϖοΫʹ͕ͨͬͯ͠σʔλΛύʔε͢Δ‣ ύʔεʹࣦഊͨ͠Β:clojure.spec/invalid͕ฦΔuser=> (s/conform (s/cat :i* (s/* integer?):s* (s/* string?))’(1 2 3 “foo” “bar”))
DPOGPSN‣ εϖοΫʹ͕ͨͬͯ͠σʔλΛύʔε͢Δ‣ ύʔεʹࣦഊͨ͠Β:clojure.spec/invalid͕ฦΔuser=> (s/conform (s/cat :i* (s/* integer?):s* (s/* string?))’(1 2 3 “foo” “bar”)){:i* [1 2 3], :s* ["foo" "bar"]}user=> (s/conform (s/cat :i* (s/* integer?):s* (s/* string?))’(1 2 “foo” 3 “bar”))
DPOGPSN‣ εϖοΫʹ͕ͨͬͯ͠σʔλΛύʔε͢Δ‣ ύʔεʹࣦഊͨ͠Β:clojure.spec/invalid͕ฦΔuser=> (s/conform (s/cat :i* (s/* integer?):s* (s/* string?))’(1 2 3 “foo” “bar”)){:i* [1 2 3], :s* ["foo" "bar"]}user=> (s/conform (s/cat :i* (s/* integer?):s* (s/* string?))’(1 2 “foo” 3 “bar”)):clojure.spec/invaliduser=>
ζϯυίΩϤγͷεϖοΫ(ns zundoko(:require [clojure.spec :as s]))(s/def ::zun*4-doko(s/cat :1 ’#{ζϯ} :2 ’#{ζϯ} :3 ’#{ζϯ} :4 ’#{ζϯ} :5 ’#{υί}))(s/def ::has-no-zun*4-doko?(fn [xs](every? #(not (s/valid? ::zun*4-doko %))(partition 5 1 xs))))(s/def ::zun-doko-kiyoshi(s/cat :preamble (s/& (s/* ’#{ζϯ υί}) ::has-no-zun*4-doko?):zun*4-doko ::zun*4-doko:kiyoshi ’#{ΩϤγ}))
ζϯυίΩϤγͷεϖοΫuser=> (s/conform ::zun-doko-kiyoshi’(υί ζϯ υίζϯ ζϯ ζϯ ζϯ υί ΩϤγ))
ζϯυίΩϤγͷεϖοΫuser=> (s/conform ::zun-doko-kiyoshi’(υί ζϯ υίζϯ ζϯ ζϯ ζϯ υί ΩϤγ)){:preamble [υί ζϯ υί],:zun*4-doko {:1 ζϯ, :2 ζϯ, :3 ζϯ, :4 ζϯ, :5 υί},:kiyoshi ΩϤγ}user=> (s/explain ::zun-doko-kiyoshi’(υί ζϯ υίζϯ ζϯ ζϯ ζϯ υί))
ζϯυίΩϤγͷεϖοΫuser=> (s/conform ::zun-doko-kiyoshi’(υί ζϯ υίζϯ ζϯ ζϯ ζϯ υί ΩϤγ)){:preamble [υί ζϯ υί],:zun*4-doko {:1 ζϯ, :2 ζϯ, :3 ζϯ, :4 ζϯ, :5 υί},:kiyoshi ΩϤγ}user=> (s/explain ::zun-doko-kiyoshi’(υί ζϯ υίζϯ ζϯ ζϯ ζϯ υί))val: () fails spec: :zundoko/zun-doko-kiyoshi predicate:(alt), Insufficient inputniluser=>
ϚΫϩͷߏจղੳ(s/def ::binding (s/cat :name simple-symbol? :init any?))(s/def ::bindings (s/and (s/* ::binding) vector?))(s/def ::with-open(s/cat :bindings ::bindings :body (s/* any?))(s/fdef with-open:args ::with-open:ret any?)user=> (s/conform ::with-open’([in (open-file)] (slurp in))){:bindings [{:name in, :init (open-file)}],:body [(slurp in)]}user=>
ϚΫϩͷߏจղੳ(defmacro with-open [bindings & body](let [[binding & more] (s/conform ::bindings bindings)](if-not binding`(do [email protected])`(let ~[(:name binding) (:init binding)](try(with-open ~(vec (s/unform ::bindings more))[email protected])(finally(.close ~(:name binding))))))))user=> (with-open [x] (slurp x)) ;;←ϚΫϩͷ͍ํΛޡΔCompilerException java.lang.IllegalArgumentException: Call tointro-to-spec.macros/with-open did not conform to spec:In: [0] val: () fails spec: :intro-to-spec.macros/bindings at:[:args :bindings :init] predicate: any?, Insufficient input:clojure.spec/args ([x] (slurp x)), compiling:(*cider-repl intro-to-spec*:1476:22)user=>
·ͱΊ‣ DMPKVSFTQFDσʔλܕؔͷ༷Λड़ޠͷΈ߹ΘͤͰهड़͢Δํ๏Λఏڙ͢Δ‣ ҰεϖοΫΛॻ͘ͱɺؔͷόϦσʔγϣϯ͚ͩͰͳ͘ɺ3&1-Ͱͷ։ൃ࣌ͷαϯϓϧσʔλੜςετɺϚΫϩͷύʔε͍Ζ͍Ζ͑Δ‣ $MPKVSFʹ͓͚Δ։ൃͷํ͕େ͖͘มΘΔՄೳੑͷ͋Δػೳ
ݱঢ়ʜ‣ ·ͩBMQIBϦϦʔεͰ"1*͕มΘΓ·ͬͯ͘Δஈ֊$MPKVSF4DSJQUͱͷฒΈଗͬͯͳ͍ͷͰ࣮ઓೖΘΓͱݫ͍͠ هɿݱঢ়Ͱ$MPKVSFBMQIBͱ$MPKVSF4DSJQUͰ"1*Ϩϕϧͷޓੑ͕͋Δ༷‣ Τϥʔϝοηʔδใෆͷঢ়ଶղফ͞Ε͍ͯΔ͕ɺݱঢ়ͰใաଟͰҰݟ͔ͯ͠Γʹ͍͘ ศརʹ͑ΔΑ͏ʹͳΔʹ։ൃڥଆͷαϙʔτඞཁ
ࢀߟจݙ‣ DMPKVSFTQFD3BUJPOBMFBOE0WFSWJFXIUUQDMPKVSFPSHBCPVUTQFD‣ TQFD(VJEFIUUQDMPKVSFPSHHVJEFTTQFD‣ $PHOJDBTUDMPKVSFTQFDXJUI3JDI)JDLFZIUUQCMPHDPHOJUFDUDPNDPHOJDBTU