Slide 1

Slide 1 text

Everyday Life Everyday Life with clojure.spec with clojure.spec

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

1. Clojure 2. Clojure 開発で困ること 3. clojure.spec

Slide 4

Slide 4 text

Clojure Clojure

Slide 5

Slide 5 text

Clojure とは Clojure とは 関数型⾔語 JVM ⾔語 Lisp 動的型付き⾔語 が作った"simple" な⾔語 Rich Hickey

Slide 6

Slide 6 text

簡単なプログラムの例 簡単なプログラムの例 dev> (defn hello [name] (println (str "Hello, " name "!"))) #'dev/hello dev> (hello "World") Hello, World! nil dev> (hello "Clojure") Hello, Clojure! nil

Slide 7

Slide 7 text

Clojure での開発のしかた Clojure での開発のしかた コンパイルが通るようにひとまとまりのコードを 書き、コンパイルできたらたいてい期待通りに動 作する 優れた型システムを備えた静的型付き⾔語の イメージ(?) 動くと思われるひとまとまりのコードを書き、動 かしてみて期待通りでなければ適宜デバッグする 典型的な動的型付き⾔語のイメージ(?)

Slide 8

Slide 8 text

REPL と繋がったエディタで⼩さな単位で動かし ながらコードを書き、書き上がったひとまとまり のコードは期待通りに動作する Clojure などLisp 系⾔語での開発スタイル いわゆる「REPL 駆動開発」 多くのLisp ではREPL 周りのツールが⾼ 度に発達している REPL と連携しながらの開発を前提に⾔ 語が設計されているとさえ考えられる

Slide 9

Slide 9 text

Clojure 開発で困ること Clojure 開発で困ること

Slide 10

Slide 10 text

例えば、こんな関数を定義する 例えば、こんな関数を定義する 様々な暗黙の前提がある( 使う側は知る由もない) (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)))

Slide 11

Slide 11 text

使ってみると 使ってみると 正しい使い⽅を知っていれば期待通りに動作する 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!"}]

Slide 12

Slide 12 text

しかし …… しかし ……

Slide 13

Slide 13 text

唐突に、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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

明らかにDB 接続情報でないものを与えると SQLException が発⽣したり example> (find-artists "foo" {}) Execution error (SQLException) at java.sql.DriverManager/getConn ection (DriverManager.java:702). No suitable driver found for foo

Slide 16

Slide 16 text

エラーメッセージが分かりづらい エラーメッセージの不親切さに定評がある fail-fast でない "garbage in, garbage out" ⼊出⼒として想定しているものが分からない ドキュメントで冗⻑かつ不明確に説明したい わけでもない 関数型⾔語なので不可解な副作⽤に悩まされ ることは少ないとはいえ……

Slide 17

Slide 17 text

従来のアプローチ 従来のアプローチ スキーマ記述とバリデーションのためのサー ドパーティライブラリ (→ ) gradual/optional typing のための準標準ライ ブラリ schema core.typed Typed Clojure

Slide 18

Slide 18 text

静的型付けの Clojure がほしい ? 静的型付けの Clojure がほしい ?

Slide 19

Slide 19 text

clojure.spec clojure.spec

Slide 20

Slide 20 text

コントラクト ( 契約 ) システム コントラクト ( 契約 ) システム 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

Slide 21

Slide 21 text

標準ライブラリ clojure.spec 標準ライブラリ clojure.spec cf. 述語 (predicate) による仕様記述システム NOT 型システム spec.alpha spec-alpha2 (alpha.spec) core.specs.alpha

Slide 22

Slide 22 text

clojure.spec を導⼊する clojure.spec を導⼊する example> (require '[clojure.spec.alpha :as s]) nil

Slide 23

Slide 23 text

この関数に "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)))

Slide 24

Slide 24 text

仕様を⾃然⾔語で表現してみると 仕様を⾃然⾔語で表現してみると 引数 ds: javax.sql.DataSource オブジェクト {:keys [name ids sort-order]}: 以下 のキーを含むかもしれない検索条件マップ :name: ⽂字列 :ids: ⾃然数の空でないシーケンス :sort-order: ソートキーのキーワー ドと昇順/ 降順の :asc または :desc の ペアの空でなく第1 要素についてユニー クなシーケンス

Slide 25

Slide 25 text

戻り値 アーティストマップのシーケンス アーティストマップ: 以下のキーを必ず 含むマップ :id: ⾃然数 :type: 1 ( グループ) または 2 ( ソロ) :name: ⽂字列

Slide 26

Slide 26 text

s/fdef s/fdef マクロで記述すると マクロで記述すると s/fdef は関数に対するspec を定義する ;;; 関数 find-artists に対する spec 定義のイメージ ;;; ,,, 部分を埋めたい (s/fdef find-artists :args (s/cat :ds ,,, ; 第 1 引数 :condition ,,,) ; 第 2 引数 :ret ,,,) ; 戻り値

Slide 27

Slide 27 text

アーティストマップを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

Slide 28

Slide 28 text

戻り値のspec 定義が定まる ;;; 関数 find-artists に対する spec 定義のイメージ ;;; ,,, 部分を埋めたい (s/fdef find-artists :args (s/cat :ds ,,, ; 第 1 引数 :condition ,,,) ; 第 2 引数 :ret (s/coll-of ::artist)) ; 戻り値

Slide 29

Slide 29 text

DataSource であることをspec として記述してみる example> (import '(javax.sql DataSource)) javax.sql.DataSource example> (s/valid? #(instance? DataSource %) (ds)) true example> (s/valid? #(instance? DataSource %) "foo") false

Slide 30

Slide 30 text

第1 引数のspec が定まる ;;; 関数 find-artists に対する spec 定義のイメージ ;;; ,,, 部分を埋めたい (s/fdef find-artists :args (s/cat :ds #(instance? DataSource %) ; 第 1 引数 :condition ,,,) ; 第 2 引数 :ret (s/coll-of ::artist)) ; 戻り値

Slide 31

Slide 31 text

: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

Slide 32

Slide 32 text

: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

Slide 33

Slide 33 text

第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

Slide 34

Slide 34 text

関数の 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]

Slide 35

Slide 35 text

改めて使ってみると 改めて使ってみると 想定通りの⼊⼒に対して変わらず動作する 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!"}]

Slide 36

Slide 36 text

そして …… そして ……

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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]

Slide 40

Slide 40 text

その他の主な活⽤⽅法 その他の主な活⽤⽅法 spec によるドキュメンテーション clojure.repl/doc の出⼒にも反映される spec によるバリデーション spec からサンプルデータの⾃動⽣成 spec によるproperty-based testing cf. test.check

Slide 41

Slide 41 text

関連サードパーティライブラリ 関連サードパーティライブラリ : spec の instrument 時のチェックを 強化する : spec のエラーメッセージを⾒やすく表 ⽰する : 標準ライブラリ関数/ マクロに対す るspec を独⾃に提供する : spec を静的解析に利⽤する試み Orchestra Expound speculative spectrum

Slide 42

Slide 42 text

clojure.spec の登場で clojure.spec の登場で Clojurian の⽇常は⼀変している Clojurian の⽇常は⼀変している イマドキのClojure 開発をぜひ体験しよう!

Slide 43

Slide 43 text

Further Reading Further Reading Clojure Clojure Clojure/ClojureScript 関連リンク集 標準ライブラリ

Slide 44

Slide 44 text

clojure.spec clojure.spec clojure.spec - Rationale and Overview ⽇本語版 spec Guide

Slide 45

Slide 45 text

clojure.spec 関連ライブラリ clojure.spec 関連ライブラリ cf. Orchestra Expound Pinpointer speculative spectrum

Slide 46

Slide 46 text

コントラクトシステム (Racket) コントラクトシステム (Racket) The Racket Guide > 7 Contracts The Racket Reference > 8 Contracts

Slide 47

Slide 47 text

サンプルコード サンプルコード lagenorhynque/everyday-life-with-clojure-spec lagenorhynque/spec-examples