Upgrade to Pro — share decks privately, control downloads, hide ads and more …

やってみる!clojure.spec

 やってみる!clojure.spec

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

OHTA Shogo

March 14, 2018
Tweet

More Decks by OHTA Shogo

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. ࠓ೔ͷ಺༰
    ‣ DMPKVSFTQFDೖ໳
    എܠ
    DMPKVSFTQFDͱ͸
    TQFDͷجຊతͳ࢖͍ํ
    ‣ DMPKVSFTQFDΛ΍ͬͯΈΔ
    ؆୯ͳαϯϓϧϓϩδΣΫτͷதͰ࢖ͬͯΈ·͠ΐ͏

    View Slide

  4. DMPKVSFTQFDೖ໳

    View Slide

  5. എܠɿ$MPKVSFͷ՝୊
    ‣ ؔ਺ΛͲ͏΍ͬͯ࢖͏ͷ͔෼͔Γʹ͍͘ʜ
    Ҿ਺ʹԿΛड͚औΔͷ͔ɺ໭Γ஋ͱͯ͠ԿΛฦ͢ͷ͔
    $MPKVSFͰ͸ͨͩͷϚοϓ΍ϕΫλΛଟ༻͢ΔͷͰɺɹ
    Ͳ͏͍͏஋͕ظ଴͞Ε͍ͯΔͷ͔൑ผ͠ʹ͍͘
    ‣ Τϥʔϝοηʔδ͕෼͔Γʹ͍͘ʜ
    $MPKVSFͷؔ਺͸جຊతʹ(BSCBHF*O(BSCBHF0VU
    ޡͬͨೖྗʹରͯ͠͸ɺ+7.ͷΤϥʔ͕ͦͷ··ग़Δ
    ͜ͱ΋ଟ͍

    View Slide

  6. DMPKVSFTQFDͱ͸
    ‣ $MPKVSFͰಋೖ͞Εͨ৽ػೳ
    ‣ ड़ޠϕʔεͰσʔλܕ΍ؔ਺ͷ࢓༷Λهड़͠ɺͦΕΛνΣοΫ͢Δ࢓૊Έ
    ੩తݕࠪͰ͸ͳ͘ɺܖ໿ϓϩάϥϛϯάʹ͍ۙ
    ‣ Ұ౓࢓༷ εϖοΫ
    Λॻ͚͹։ൃͷ༷ʑͳ৔໘Ͱར༻Մ
    υΩϡϝϯςʔγϣϯ
    όϦσʔγϣϯ
    σʔλੜ੒
    ϓϩύςΟϕʔεςετ
    ϚΫϩͷߏจνΣοΫ

    View Slide

  7. s/valid?
    ‣ (s/valid? <εϖοΫ> <஋>)
    ஋͕εϖοΫΛຬ͍ͨͯ͠Δ͔ΛνΣοΫ͢Δ
    (require ‘[clojure.spec.alpha :as s])
    (s/valid? int? 42)
    ;; => true
    (s/valid? int? :foo)
    ;; => false

    View Slide

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

    View Slide

  9. εϖοΫʹ໊લΛ͚ͭΔ
    ‣ s/defͰεϖοΫʹ໊લΛ͚ͭΔ͜ͱ͕Ͱ͖Δ
    ‣ εϖοΫΛ࠶ར༻Ͱ͖Δ
    (s/def ::id int?)
    (s/def :person/name string?)
    (s/valid? ::id 42)
    ;; => true
    (s/valid? :person/name “Rich Hickey”)
    ;; => true

    View Slide

  10. ू߹΋εϖοΫ
    ‣ ू߹#{e1 … en}͸e1
    ʜ en
    ͷ͍ͣΕ͔ͱ౳͍͠Α͏ͳ
    ஋Λද͢εϖοΫ
    ‣ ڞ༻ମͷΑ͏ͳ஋Λදݱ͢Δͷʹศར
    (s/valid? #{0 1 2} 3)
    ;; => false
    (s/valid? #{0 1 2} 2)
    ;; => true

    View Slide

  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

    View Slide

  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

    View Slide

  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͕ͳ͍ͱݴ͍ͬͯΔ

    View Slide

  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]

    View Slide

  15. ؔ਺ͷεϖοΫ
    ‣ ؔ਺ͷҾ਺͓Αͼ໭Γ஋ͷεϖοΫΛهड़Ͱ͖Δ
    ‣ :fnͰҾ਺ͱ໭Γ஋ͷؒͷؔ܎ʹ͍ͭͯ΋نఆͰ͖Δ
    (defn square [x]
    (* x x))
    (s/fdef square
    :args (s/cat :x int?)
    :ret int?)

    View Slide

  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)

    View Slide

  17. st/check
    ‣ Ҿ਺ͷεϖοΫ͔ΒϥϯμϜͳೖྗΛ࡞ͬͯؔ਺ʹ౉͠ɺ
    ໭Γ஋͕໭Γ஋ͷεϖοΫΛຬ͔ͨ͢Ͳ͏͔ΛνΣοΫ
    (st/check `square)
    ;; ({:spec
    #object[clojure.spec.alpha$fspec_impl$reify__2451
    0x7caa6a11
    "[email protected]"
    ], :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]}] …

    View Slide

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

    View Slide

  19. DMPKVSFTQFDΛ΍ͬͯΈΔ

    View Slide

  20. ՝୊ɿλεΫ؅ཧ
    ‣ ΠϯϝϞϦͰ3&1-͔Β࢖͏λεΫ؅ཧ
    ‣ IUUQTHJUIVCDPNBUIPTTQFDFYBNQMF
    ‣ ࢓༷
    λεΫ͸*%ͱઆ໌ EFTDSJQUJPO
    ɺঢ়ଶ TUBUVT
    Λ࣋ͭ
    λεΫϦετ͸ݸҎ্ͷλεΫΛอ࣋Ͱ͖Δ
    λεΫϦετʹ*%Λࢦఆͯ͠λεΫΛऔಘͨ͠ΓɺλεΫͷઆ
    ໌΍ঢ়ଶΛมߋͨ͠ΓͰ͖Δ
    λεΫͷঢ়ଶ͸QFOEJOH͔EPOFͷ͍ͣΕ͔ ॳظ͸QFOEJOH

    View Slide

  21. ͸͡Ί͔ͨ
    ‣ 3&1-Λىಈ
    ‣ ΋͘͠͸
    ‣ 3&1-ͰҎԼΛೖྗ
    $ clj -Adev
    $ lein repl
    (goto ‘spec-example.todo)

    View Slide

  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 {}})

    View Slide

  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}

    View Slide

  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}}}

    View Slide

  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)

    View Slide

  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)

    View Slide

  27. add-taskΛcheckͯ͠ΈΔ
    (st/check `add-task)
    ;; =>
    ({:spec
    #object[clojure.spec.alpha$fspec_impl$reify
    __2451 0x5e62054e
    [email protected]
    5e62054e"],
    :clojure.spec.test.check/ret
    {:result true,
    :num-tests 1000,
    :seed 1521012250499},
    :sym spec-example.todo/add-task})

    View Slide

  28. checkΛ௨ͯ͠ΈΔ
    ‣ count-tasksͱall-tasksʹਖ਼͍͠εϖοΫΛఆٛ͠
    ͯɺcheckΛ௨ͯ͠Έ·͠ΐ͏
    (defn count-tasks [tasks]
    (count (:items tasks)))
    (defn all-tasks [tasks]
    (sequence (vals (:items tasks))))

    View Slide

  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))

    View Slide

  30. ͕࣌ؒ͋Ε͹
    ϓϩύςΟϕʔεςετͷ঺հ

    View Slide

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

    View Slide

  32. ͓͢͢ΊϥΠϒϥϦ
    ‣ 0SDIFTUSBinstrumentͰ໭Γ஋΋νΣοΫͯ͘͠ΕΔ
    IUUQTHJUIVCDPNKFBZFPSDIFTUSB
    ‣ UFTUDIVDLUFTUDIFDLΛ࢖͏ͱ͖ͷϢʔςΟϦςΟू
    IUUQTHJUIVCDPNHGSFEFSJDLTUFTUDIVDL
    ‣ &YQPVOEFYQMBJOͷ݁ՌΛݟ΍͘͢੔ܗͯ͘͠ΕΔ
    IUUQTHJUIVCDPNCICFYQPVOE
    ‣ 1JOQPJOUFSಉ্
    IUUQTHJUIVCDPNBUIPT1JOQPJOUFS

    View Slide

  33. ϦϯΫू
    ‣ DMPKVSFTQFDͷ֓ཁ ೔ຊޠʂ

    IUUQTKBQBODMPKVSJBOTHJUIVCJPDMPKVSFTJUFKBBCPVUTQFDIUNM
    ‣ DMPKVSFTQFDΨΠυ
    IUUQTDMPKVSFPSHHVJEFTTQFD
    ‣ 1SPHSBNNJOH$MPKVSF SEFE
    IUUQTQSBHQSPHDPNCPPLTIDMPKQSPHSBNNJOHDMPKVSFUIJSEFEJUJPO

    View Slide