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

Everyday Life with clojure.spec

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Everyday Life with clojure.spec

clojure.specの登場で変わったClojureプログラマの日常。
イマドキのClojure開発を体験しよう!

Avatar for Kent OHASHI

Kent OHASHI

April 17, 2020

More Decks by Kent OHASHI

Other Decks in Programming

Transcript

  1. lagénorhynque lagénorhynque (defprofile lagénorhynque :id @lagenorhynque :reading "/laʒenɔʁɛ̃ k/" :aliases

    [" カマイルカ "] :languages [Clojure Haskell English français] :interests [programming language-learning law mathematics] :commits ["github.com/lagenorhynque/duct.module.pedestal" "github.com/lagenorhynque/duct.module.cambium"] :contributes ["github.com/japan-clojurians/clojure-site-ja"])
  2. 簡単なプログラムの例 簡単なプログラムの例 dev> (defn hello [name] (println (str "Hello, "

    name "!"))) #'dev/hello dev> (hello "World") Hello, World! nil dev> (hello "Clojure") Hello, Clojure! nil
  3. 例えば、こんな関数を定義する 例えば、こんな関数を定義する 様々な暗黙の前提がある( 使う側は知る由もない) (defn find-artists [ds {:keys [name ids

    sort-order]}] (jdbc/execute! ds (cond-> (sql/build :select :* :from :artist) name (merge-where [:like :name (str \% name \%)]) (seq ids) (merge-where [:in :id ids]) (seq sort-order) (#(apply merge-order-by % sort-order)) (empty? sort-order) (merge-order-by [:id :asc]) true sql/format)))
  4. 使ってみると 使ってみると 正しい使い⽅を知っていれば期待通りに動作する example> (find-artists (ds) {}) [#:artist{:id 1, :type

    1, :name "Aqours"} #:artist{:id 2, :type 1, :name "CYaRon!"} #:artist{:id 3, :type 1, :name "AZALEA"} #:artist{:id 4, :type 1, :name "Guilty Kiss"} #:artist{:id 5, :type 1, :name "Saint Snow"} #:artist{:id 6, :type 1, :name "Saint Aqours Snow"}] example> (find-artists (ds) {:name "Aq"}) [#:artist{:id 1, :type 1, :name "Aqours"} #:artist{:id 6, :type 1, :name "Saint Aqours Snow"}] example> (find-artists (ds) {:ids [2]}) [#:artist{:id 2, :type 1, :name "CYaRon!"}]
  5. 唐突に、Long からISeq を作る⽅法が分からない と⾔われたり Clojurian にはお馴染み example> (find-artists (ds) {:ids

    2}) Execution error (IllegalArgumentException) at everyday-life-with -clojure-spec.example/find-artists (example.clj:40). Don't know how to create ISeq from: java.lang.Long
  6. PostgreSQL にアクセスするので⼊⼒の型が想定 と違うとPSQLException が発⽣したり example> (find-artists (ds) {:ids ["2"]}) Execution

    error (PSQLException) at org.postgresql.core.v3.QueryE xecutorImpl/receiveErrorResponse (QueryExecutorImpl.java:2533). ERROR: operator does not exist: bigint = character varying Hint: No operator matches the given name and argument types. Y ou might need to add explicit type casts. Position: 32
  7. 明らかにDB 接続情報でないものを与えると SQLException が発⽣したり example> (find-artists "foo" {}) Execution error

    (SQLException) at java.sql.DriverManager/getConn ection (DriverManager.java:702). No suitable driver found for foo
  8. コントラクト ( 契約 ) システム コントラクト ( 契約 ) システム

    e.g. Racket のcontract system > (define/contract (maybe-invert i b) (-> integer? boolean? integer?) (if b (- i) i)) > (maybe-invert 1 #t) -1 > (maybe-invert #f 1) maybe-invert: contract violation expected: integer? given: #f in: the 1st argument of (-> integer? boolean? integer?) contract from: (function maybe-invert) blaming: top-level (assuming the contract is correct) at: eval:2.0 The Racket Reference > 8.2 Function Contracts
  9. この関数に "spec" を付けたい この関数に "spec" を付けたい (defn find-artists [ds {:keys

    [name ids sort-order]}] (jdbc/execute! ds (cond-> (sql/build :select :* :from :artist) name (merge-where [:like :name (str \% name \%)]) (seq ids) (merge-where [:in :id ids]) (seq sort-order) (#(apply merge-order-by % sort-order)) (empty? sort-order) (merge-order-by [:id :asc]) true sql/format)))
  10. 仕様を⾃然⾔語で表現してみると 仕様を⾃然⾔語で表現してみると 引数 ds: javax.sql.DataSource オブジェクト {:keys [name ids sort-order]}:

    以下 のキーを含むかもしれない検索条件マップ :name: ⽂字列 :ids: ⾃然数の空でないシーケンス :sort-order: ソートキーのキーワー ドと昇順/ 降順の :asc または :desc の ペアの空でなく第1 要素についてユニー クなシーケンス
  11. s/fdef s/fdef マクロで記述すると マクロで記述すると s/fdef は関数に対するspec を定義する ;;; 関数 find-artists

    に対する spec 定義のイメージ ;;; ,,, 部分を埋めたい (s/fdef find-artists :args (s/cat :ds ,,, ; 第 1 引数 :condition ,,,) ; 第 2 引数 :ret ,,,) ; 戻り値
  12. アーティストマップをspec として記述してみる s/def はspec(= 述語) に名前を付ける s/valid? はspec を満たすかどうか判定する example>

    (s/def :artist/id nat-int?) :artist/id example> (s/def :artist/type #{1 2}) :artist/type example> (s/def :artist/name string?) :artist/name example> (s/def ::artist (s/keys :req [:artist/id :artist/type :artist/name])) :everyday-life-with-clojure-spec.example/artist example> (s/valid? ::artist #:artist{:id 1 :type 2 :name "You Watanabe"}) true
  13. 戻り値のspec 定義が定まる ;;; 関数 find-artists に対する spec 定義のイメージ ;;; ,,,

    部分を埋めたい (s/fdef find-artists :args (s/cat :ds ,,, ; 第 1 引数 :condition ,,,) ; 第 2 引数 :ret (s/coll-of ::artist)) ; 戻り値
  14. DataSource であることをspec として記述してみる example> (import '(javax.sql DataSource)) javax.sql.DataSource example> (s/valid?

    #(instance? DataSource %) (ds)) true example> (s/valid? #(instance? DataSource %) "foo") false
  15. 第1 引数のspec が定まる ;;; 関数 find-artists に対する spec 定義のイメージ ;;;

    ,,, 部分を埋めたい (s/fdef find-artists :args (s/cat :ds #(instance? DataSource %) ; 第 1 引数 :condition ,,,) ; 第 2 引数 :ret (s/coll-of ::artist)) ; 戻り値
  16. :ids キーの値をspec として記述してみる example> (s/def ::ids (s/coll-of :artist/id :min-count 1))

    :everyday-life-with-clojure-spec.example/ids example> (s/valid? ::ids []) false example> (s/valid? ::ids [2]) true example> (s/valid? ::ids [2 4]) true example> (s/valid? ::ids [2 2]) true
  17. :sort-order キーの値をspec として記述してみる example> (s/def ::sort-order (s/and (s/coll-of (s/tuple #{:id

    :type :name} #{:asc :desc}) :min-count 1) #(apply distinct? (map first %)))) :everyday-life-with-clojure-spec.example/sort-order example> (s/valid? ::sort-order []) false example> (s/valid? ::sort-order [[:name :asc] [:id :desc]]) true example> (s/valid? ::sort-order [[:name :misc] [:id :desc]]) false example> (s/valid? ::sort-order [[:name :asc] [:name :desc]]) false
  18. 第2 引数のspec が定まり、関数のspec が仕上がる ex> (s/fdef find-artists :args (s/cat :ds

    #(instance? DataSource %) :condition (s/keys :opt-un [:artist/name ::ids ::sort-order])) :ret (s/coll-of ::artist)) everyday-life-with-clojure-spec.example/find-artists
  19. 関数の spec を実装に組み込む 関数の spec を実装に組み込む (instrumentation) (instrumentation) stest/instrument は関数のspec

    の引数に対す るチェックを関数の実装に組み込む 実際の開発環境では開発/ テスト時に⾃動的 に組み込まれるように設定することが多い example> (require '[clojure.spec.test.alpha :as stest]) nil example> (stest/instrument `find-artists) [everyday-life-with-clojure-spec.example/find-artists]
  20. 改めて使ってみると 改めて使ってみると 想定通りの⼊⼒に対して変わらず動作する example> (find-artists (ds) {}) [#:artist{:id 1, :type

    1, :name "Aqours"} #:artist{:id 2, :type 1, :name "CYaRon!"} #:artist{:id 3, :type 1, :name "AZALEA"} #:artist{:id 4, :type 1, :name "Guilty Kiss"} #:artist{:id 5, :type 1, :name "Saint Snow"} #:artist{:id 6, :type 1, :name "Saint Aqours Snow"}] example> (find-artists (ds) {:name "Aq"}) [#:artist{:id 1, :type 1, :name "Aqours"} #:artist{:id 6, :type 1, :name "Saint Aqours Snow"}] example> (find-artists (ds) {:ids [2]}) [#:artist{:id 2, :type 1, :name "CYaRon!"}]
  21. spec に違反すると直ちにエラーになってくれる ⼊⼒のどの値がどのspec に違反しているか教え てくれる example> (find-artists (ds) {:ids 2})

    Execution error - invalid arguments to everyday-life-with-clojur e-spec.example/find-artists at (form-init8369102478102661347.clj :747). 2 - failed: coll? at: [:condition :ids] spec: :everyday-life-wit h-clojure-spec.example/ids
  22. example> (find-artists (ds) {:ids ["2"]}) Execution error - invalid arguments

    to everyday-life-with-clojur e-spec.example/find-artists at (form-init8369102478102661347.clj :753). "2" - failed: nat-int? at: [:condition :ids] spec: :artist/id
  23. example> (find-artists "foo" {}) Execution error - invalid arguments to

    everyday-life-with-clojur e-spec.example/find-artists at (form-init8369102478102661347.clj :750). "foo" - failed: (instance? javax.sql.DataSource %) at: [:ds]
  24. 関連サードパーティライブラリ 関連サードパーティライブラリ : spec の instrument 時のチェックを 強化する : spec

    のエラーメッセージを⾒やすく表 ⽰する : 標準ライブラリ関数/ マクロに対す るspec を独⾃に提供する : spec を静的解析に利⽤する試み Orchestra Expound speculative spectrum