やってみる!clojure.spec

 やってみる!clojure.spec

2018/3/14のclj-nakano #5の資料です。

E170ae2eb0adcf3d136ab0990f0ea671?s=128

OHTA Shogo

March 14, 2018
Tweet

Transcript

  1. ΍ͬͯΈΔʂDMPKVSFTQFD DMKOBLBOP !BUIPT

  2. ࣗݾ঺հ ‣ 5XJUUFS!BUIPT ‣ (JU)VCBUIPT ‣ χϟϯύεגࣜձࣾॴଐ ‣ $MPKVSFίϯτϦϏϡʔλ

  3. ࠓ೔ͷ಺༰ ‣ DMPKVSFTQFDೖ໳  എܠ  DMPKVSFTQFDͱ͸  TQFDͷجຊతͳ࢖͍ํ ‣

    DMPKVSFTQFDΛ΍ͬͯΈΔ  ؆୯ͳαϯϓϧϓϩδΣΫτͷதͰ࢖ͬͯΈ·͠ΐ͏
  4. DMPKVSFTQFDೖ໳

  5. എܠɿ$MPKVSFͷ՝୊ ‣ ؔ਺ΛͲ͏΍ͬͯ࢖͏ͷ͔෼͔Γʹ͍͘ʜ  Ҿ਺ʹԿΛड͚औΔͷ͔ɺ໭Γ஋ͱͯ͠ԿΛฦ͢ͷ͔  $MPKVSFͰ͸ͨͩͷϚοϓ΍ϕΫλΛଟ༻͢ΔͷͰɺɹ Ͳ͏͍͏஋͕ظ଴͞Ε͍ͯΔͷ͔൑ผ͠ʹ͍͘ ‣ Τϥʔϝοηʔδ͕෼͔Γʹ͍͘ʜ

     $MPKVSFͷؔ਺͸جຊతʹ(BSCBHF*O(BSCBHF0VU  ޡͬͨೖྗʹରͯ͠͸ɺ+7.ͷΤϥʔ͕ͦͷ··ग़Δ ͜ͱ΋ଟ͍
  6. DMPKVSFTQFDͱ͸ ‣ $MPKVSFͰಋೖ͞Εͨ৽ػೳ ‣ ड़ޠϕʔεͰσʔλܕ΍ؔ਺ͷ࢓༷Λهड़͠ɺͦΕΛνΣοΫ͢Δ࢓૊Έ  ੩తݕࠪͰ͸ͳ͘ɺܖ໿ϓϩάϥϛϯάʹ͍ۙ ‣ Ұ౓࢓༷ εϖοΫ

    Λॻ͚͹։ൃͷ༷ʑͳ৔໘Ͱར༻Մ  υΩϡϝϯςʔγϣϯ  όϦσʔγϣϯ  σʔλੜ੒  ϓϩύςΟϕʔεςετ  ϚΫϩͷߏจνΣοΫ
  7. s/valid? ‣ (s/valid? <εϖοΫ> <஋>)  ஋͕εϖοΫΛຬ͍ͨͯ͠Δ͔ΛνΣοΫ͢Δ (require ‘[clojure.spec.alpha :as

    s]) (s/valid? int? 42) ;; => true (s/valid? int? :foo) ;; => false
  8. ड़ޠ͸εϖοΫʹͳΔ ‣ ೚ҙͷड़ޠ ਅِ஋Λฦؔ͢਺ ΛεϖοΫͱͯ͠࢖͑Δ ‣ ૊ࠐΈؔ਺͚ͩͰͳ͘Ϣʔβఆٛؔ਺Ͱ΋ແ໊ؔ਺Ͱ΋Մ (s/valid? string? “foo”)

    ;; => true (s/valid? #(> % 10) 11) ;; => true
  9. εϖοΫʹ໊લΛ͚ͭΔ ‣ s/defͰεϖοΫʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ ‣ εϖοΫΛ࠶ར༻Ͱ͖Δ (s/def ::id int?) (s/def :person/name

    string?) (s/valid? ::id 42) ;; => true (s/valid? :person/name “Rich Hickey”) ;; => true
  10. ू߹΋εϖοΫ ‣ ू߹#{e1 … en}͸e1 ʜ en ͷ͍ͣΕ͔ͱ౳͍͠Α͏ͳ ஋Λද͢εϖοΫ ‣

    ڞ༻ମͷΑ͏ͳ஋Λදݱ͢Δͷʹศར (s/valid? #{0 1 2} 3) ;; => false (s/valid? #{0 1 2} 2) ;; => true
  11. ίϨΫγϣϯͷεϖοΫ ‣ 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
  12. ϚοϓͷεϖοΫ ‣ 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
  13. 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) ˡ:id͸int?ͩͱݴ͍ͬͯΔ ˡ:name͕ͳ͍ͱݴ͍ͬͯΔ
  14. 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]
  15. ؔ਺ͷεϖοΫ ‣ ؔ਺ͷҾ਺͓Αͼ໭Γ஋ͷεϖοΫΛهड़Ͱ͖Δ ‣ :fnͰҾ਺ͱ໭Γ஋ͷؒͷؔ܎ʹ͍ͭͯ΋نఆͰ͖Δ (defn square [x] (* x

    x)) (s/fdef square :args (s/cat :x int?) :ret int?)
  16. 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)
  17. 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]}] …
  18. ࢀߟɿsquareͷగਖ਼൛ ‣ *͸MPOHͷൣғͷ੔਺ಉ࢜ͷֻ͚ࢉ͸Φʔόʔϑϩʔ͢Δ Մೳੑ͕͋ΔͷͰ୅ΘΓʹ*’Λ࢖͏ ‣ #JH*OU͸JOU Ͱ͸ͳ͍ͷͰJOUFHFS Λ࢖͏ (defn square

    [x] (*’ x x)) (s/fdef square :args (s/cat :x int?) :ret integer?)
  19. DMPKVSFTQFDΛ΍ͬͯΈΔ

  20. ՝୊ɿλεΫ؅ཧ ‣ ΠϯϝϞϦͰ3&1-͔Β࢖͏λεΫ؅ཧ ‣ IUUQTHJUIVCDPNBUIPTTQFDFYBNQMF ‣ ࢓༷  λεΫ͸*%ͱઆ໌ EFTDSJQUJPO

    ɺঢ়ଶ TUBUVT Λ࣋ͭ  λεΫϦετ͸ݸҎ্ͷλεΫΛอ࣋Ͱ͖Δ  λεΫϦετʹ*%Λࢦఆͯ͠λεΫΛऔಘͨ͠ΓɺλεΫͷઆ ໌΍ঢ়ଶΛมߋͨ͠ΓͰ͖Δ  λεΫͷঢ়ଶ͸QFOEJOH͔EPOFͷ͍ͣΕ͔ ॳظ͸QFOEJOH
  21. ͸͡Ί͔ͨ ‣ 3&1-Λىಈ ‣ ΋͘͠͸ ‣ 3&1-ͰҎԼΛೖྗ $ clj -Adev

    $ lein repl (goto ‘spec-example.todo)
  22. λεΫͷεϖοΫ (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 {}})
  23. λεΫΛੜ੒ͯ͠ΈΔ (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}
  24. λεΫϦετΛੜ੒ͯ͠ΈΔ (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}}}
  25. 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)
  26. 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)
  27. 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})
  28. checkΛ௨ͯ͠ΈΔ ‣ count-tasksͱall-tasksʹਖ਼͍͠εϖοΫΛఆٛ͠ ͯɺcheckΛ௨ͯ͠Έ·͠ΐ͏ (defn count-tasks [tasks] (count (:items tasks)))

    (defn all-tasks [tasks] (sequence (vals (:items tasks))))
  29. 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))
  30. ͕࣌ؒ͋Ε͹ ϓϩύςΟϕʔεςετͷ঺հ

  31. ·ͱΊ ‣ DMPKVSFTQFDͰσʔλ΍ؔ਺ͷεϖοΫΛఆٛ͢Δ͜ͱ Ͱɺ$MPKVSFͰΑΓݎ࿚ͳίʔυΛॻ͚ΔΑ͏ʹ ‣ Ұ౓εϖοΫΛఆ͓ٛͯ͘͠ͱɺ։ൃ࣌ɾςετ࣌ͳͲ ༷ʑͳ৔໘ͰεϖοΫΛԠ༻͢Δ͜ͱ͕Ͱ͖Δ ‣ ͨͩ͠ɺ৽͔ͭ͘͠ಠಛͷػೳͳͨΊɺ·ͩ·ͩϕετ ϓϥΫςΟε΋ݻ·͍ͬͯͳ͍෦෼΋ଟ͍

    ‣ ΈΜͳͰҰॹʹ΍͍͖ͬͯ·͠ΐ͏
  32. ͓͢͢ΊϥΠϒϥϦ ‣ 0SDIFTUSBinstrumentͰ໭Γ஋΋νΣοΫͯ͘͠ΕΔ IUUQTHJUIVCDPNKFBZFPSDIFTUSB ‣ UFTUDIVDLUFTUDIFDLΛ࢖͏ͱ͖ͷϢʔςΟϦςΟू IUUQTHJUIVCDPNHGSFEFSJDLTUFTUDIVDL ‣ &YQPVOEFYQMBJOͷ݁ՌΛݟ΍͘͢੔ܗͯ͘͠ΕΔ IUUQTHJUIVCDPNCICFYQPVOE

    ‣ 1JOQPJOUFSಉ্ IUUQTHJUIVCDPNBUIPT1JOQPJOUFS
  33. ϦϯΫू ‣ DMPKVSFTQFDͷ֓ཁ ೔ຊޠʂ  IUUQTKBQBODMPKVSJBOTHJUIVCJPDMPKVSFTJUFKBBCPVUTQFDIUNM ‣ DMPKVSFTQFDΨΠυ IUUQTDMPKVSFPSHHVJEFTTQFD ‣

    1SPHSBNNJOH$MPKVSF SEFE IUUQTQSBHQSPHDPNCPPLTIDMPKQSPHSBNNJOHDMPKVSFUIJSEFEJUJPO