Skip to content

Instantly share code, notes, and snippets.

@fanannan
Last active August 29, 2015 13:57
Show Gist options
  • Save fanannan/9579301 to your computer and use it in GitHub Desktop.
Save fanannan/9579301 to your computer and use it in GitHub Desktop.
Schemaの紹介
Schemaとは
・Prismatic社製の型チェックライブラリ
・最新版は “0.2.1”
https://github.com/prismatic/schema
型チェック??
・なんでClojureで型チェック?
・でも、あえて型チェックしたいなら?
普通に関数の本体でチェックする
(defn sample [x]
(if (number? x)
(+ x 1)
(throw (Exception. “error!”))))
preでチェックする
(defn sample [x]
{:pre [(number? x)]
(+ x 1))
core.typedでチェックする
(require '[clojure.core.typed :as ty])
(ty/ann sample [Number])
(defn sample [x]
(+ x 1))
Schemaを使うと?
(require '[schema.macro :as sm])
(sm/defn sample [x :- Number]
(+ x 1))
なんでSchema?
・ コードが分かりやすくなる
・ 必要なところで使える
・ マップのチェックができる
・ 型の定義が柔軟かつ簡単明快
・ 複雑な構造もチェックできる
・ 速くなる
副作用のない純粋な関数の例
(defn update-share-counts [share-counts updates]
(reduce
(fn [result [user-id share-type delta]]
(update-in result
[share-type (str user-id)]
(fnil + 0)
delta)))
share-counts
updates))
じゃあ
(defn update-share-counts
"Increment share-counts according to the share actions in updates:
share-counts: map from user-id to map of share-type
(must be one of :twitter, :facebook, or :email) to
count of number of shares (a long)
updates: sequence of triples [user-id share-type delta], where delta is
amount to increment share-type by
returns updated share-counts reflecting shares in updates"
[share-counts updates]
こうなる
(sm/defn update-share-counts :- UserShareCounts
[share-counts :- UserShareCounts
updates :- [UserShareUpdate]]
(reduce
(fn [result [user-id share-type delta]]
(update-in result
[share-type (str user-id)]
(fnil + 0)
delta))
share-counts
updates))
コードが分かりやすくなる
・ 関数の引数や返値の型がひと目で分かる。
・ Prismatic: ”ドキュメント文字列より関数の定義の方が速く変化する”
(sm/defn make-train-dataset :- DataSet
"モデル訓練用データセットの作成"
[r :- Resource,
cfg :- Config,
cat :- Category,
job-type :- Keyword,
job-desc :- Job,
model-info :- Model-info]
ちょっと覗いて見ると
(sm/defn sample :- String
"数値を文字列に変換"
[x :- Long]
(str x))
(clojure.pprint/pprint (meta #'sample))
{:arglists ([x]),
:ns #<Namespace schema-test.core>,
:name sample,
:column 1,
:raw-arglists ([x :- Long]),
:doc "Inputs: [x :- Long]\n Returns: String\n\n 数値を文字列に変換",
:schema {:output-schema java.lang.String,
:input-schemas ([{:schema java.lang.Long, :optional? false, :name x}])},
:line 1,
:file "/tmp/form-init1376864852441125993.clj",
:tag java.lang.String}
必要なところで使える
・ 関数や無名関数の引数、返値や、関数本体の中での型チェックなどが可能
- Schema版のdefn, fn, letfn, defrecord等
- validate, checkなどの型判定関数
(sm/defn sample :- Number
[x :- Number]
(s/validate Number (+ x 1)))
マップの構造のチェックができる
・マップのキーや要素についてのチェックができる
{:name String, :age Integer}
{:name String, (s/optional-key :age) Integer}
”型”の定義が柔軟かつ簡単明快
・Javaのクラスがそのまま使える(instance?と同様)
・複合的な”型”が定義できる
(s/either java.lang.Byte Boolean)
(s/both Number (s/pred pos?))
複雑な構造もチェックできる
・入れ子や再帰的な”型”の定義も可能
(def Record {:id Number, :name String})
(def Structure {:tag Keyword [Record])})
速くなる
・型チェックをするモードと、しないモードの使い分けができる。
・タイプヒントとして機能する。
Schemaの使い方
project.clj
(defproject schema-test "0.1.0-SNAPSHOT"
:dependencies
[[org.clojure/clojure "1.5.1"]
; Schema
[prismatic/schema "0.2.1"]
:main schema-test.core)
オーバーヘッドは?
(require '[clojure.core.typed :as ty])
(require '[criterium.core :as cr])
(defn no-check [x](+ x 1))
(defn pre-check [x]{:pre [(number? x)]}(+ x 1))
(defn body-check [x](when (number? x)(+ x 1)))
(defn hint [^Number x](+ x 1))
(defn ^Number hint2 [^Number x](+ x 1))
(ty/ann typed-check [Number -> Number])
(defn typed-check [x](+ x 1))
(sm/defn schema-no-check [x](+ x 1))
(sm/defn schema-arg-check [x :- Number](+ x 1))
(sm/defn schema-arg-check2 :- Number [x :- Number](+ x 1))
(sm/defn ^:always-validate schema-always-arg-check [x :- Number](+ x 1))
ベンチマーク結果
Prismatic: “オーバーヘッドは5%くらい”
メリットまとめ
・コードの可読性が増し、メンテしやすい。
・ドキュメント文字列に気を使うことが減る。
・型チェックは必要なとき、必要なところだけ有効にすれば良いので、速度のハンデは事実上ない。むしろ、タイプヒントの効果も期待できる(かも)。
・とにかく、使いやすい
でもまだα版です・・・・。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment