Clojure  spec 和訳

Clojure  spec 和訳
※まだ途中です。間違いや分かりにくいところなどがありましたら、ご指摘いただけると嬉しいです。

# Getting started
specライブラリはデータ構造を明示し、その構造を検証・破棄し、specに基づいたデータを生成することができます。

specを使う際は、最新のアルファ版（または下記以降の）のClojureを使用してください。

```clojure
[org.clojure/clojure "1.9.0-alpha16"]
```

まずはじめに、`clojure.spec.alpha` をrequireしてREPLにspecの名前空間を作成します。

```clojure
(require '[clojure.spec.alpha :as s])
```

または、自分が作成した名前空間でrequireすることでもspecを使用可能です。

```clojure
(ns my.ns
  (:require [clojure.spec.alpha :as s]))
```


# Predicates
各specは、許可できる値のsetを表しています。specを構築する方法はいくつかありますが、何れも、より洗練されたspecを生成することができます。

引数が一つで真理値を返すような既存のClojureの関数は、何れも検証用の `述語` となります。
それでは、ある値がspecと一致（conform）しているのかどうかを、 `conform` を使って確かめてみましょう。

```clojure
(s/conform even? 1000)
;;=> 1000
```

`conform` 関数は、specへと変換可能な何かと、値を引数とします（この例では、even?が「specへと変換可能な何か」で、値が「1000」）。
述語（even?）は暗黙的にspecへと変換され、”specに一致（conformed）した”値が返り値として返却されます。今回の例では、一致した値は元の値と同じです。
では、specに一致しない場合はどうでしょうか。
値がspecを満たさなかったときには特別な返り値、 `:clojure.spec.alpha/invalid` が返されます。

もしも、”specに一致（conformed）した”値や `:clojure.spec.alpha/invalid` を使わないで、単に一致しているか否かの確認をしたいだけというときには、返り値がbooleanになる `valid?` を代わりに使うと良いかもしれません。

```clojure
(s/valid? even? 10)
;;=> true
```

繰り返しますが、ここで `valid?` は暗黙的に述語からspecへと変換されていることに注意してください。specライブラリは、上記のようにClojureの関数全てを活用可能にしています（述語の特別な辞書などはありません）。さらにいくつかの例を見ていきましょう。

```clojure
(s/valid? nil? nil)  ;; true
(s/valid? string? "abc")  ;; true

(s/valid? #(> % 5) 10) ;; true
(s/valid? #(> % 5) 0) ;; false

(import java.util.Date)
(s/valid? inst? (Date.))  ;; true
```

セットもまた、”値が含まれるか否か”をチェックする述語として使われます。

```clojure
(s/valid? #{:club :diamond :heart :spade} :club) ;; true
(s/valid? #{:club :diamond :heart :spade} 42) ;; false

(s/valid? #{42} 42) ;; true
```

# Registry
ここまでは、specを直接使ってきました。しかしspecライブラリは、よく使うspecをグローバルに宣言できるよう、セントラルレジストリというものを提供しています。このレジストリは名前空間に登録されたキーワードと登録した仕様を関連付けてくれます。この名前空間を使用することによって、「ライブラリやアプリケーションを横断しても、再利用可能でコンフリクトを起こさないspecを定義できる」ということが保証できます。

specの登録には `def` を使います。
分かりやすい名前空間へ仕様を登録するか否かはあなた次第です（あなたが管理している名前空間においては尚更です）。

```clojure
(s/def ::date inst?)
(s/def ::suit #{:club :diamond :heart :spade})
```

登録されたspecの識別子は、私たちがここまでに見てきた `conform` や `valid?` のように、specを定義する際に使用することができます。

```clojure
(s/valid? ::date (Date.))
;;=> true
(s/conform ::suit :club)
;;=> :club
```

後述しますが、登録されたspecは私たちがspecを構築したところであれば、どこでも使うことができます。

> ### Spec Names
>
> このガイドではしばしば、 `::date` のように、自動的に名前解決されたキーワードが使われます。Clojureリーダーは現在の名前空間を使って、完全修飾されたキーワードへと名前解決しています。また、`:animal/dog` のように完全修飾されたキーワードを使ってspecを指定しているケースもあります。
>
> 一般的にClojureのコードは、他のspec利用者とコンフリクトが起こらないよう、十分にユニークなキーワードの名前空間を使うべきです。もしもあなたが公で使用されるためのライブラリを書いているのなら、仕様の名前空間にはコンフリクトを起こさないような単語（プロジェクト名、URL、または組織名など）を含めるべきでしょう。
> 反対にプライベートな環境で開発をするのであれば、より短い名前でも良いでしょう（大切なのは、名称が十分にユニークでコンフリクトを起こさないことです）。

# Composing predicates

specを構築するもっともシンプルな方法は、 `and` と `or` を使う方法です。
`s/and` を使って、いくつかの述語を結合させ、specをつくってみましょう。

```clojure
(s/def ::big-even (s/and int? even? #(> % 1000)))
(s/valid? ::big-even :foo) ;; false
(s/valid? ::big-even 10) ;; false
(s/valid? ::big-even 100000) ;; true
```

`s/or` を使って、二つの選択肢を指定することもできます。

```clojure
(s/def ::name-or-id (s/or :name string?
                          :id   int?))
(s/valid? ::name-or-id "abc") ;; true
(s/valid? ::name-or-id 100) ;; true
(s/valid? ::name-or-id :foo) ;; false
```

この `or` は、妥当性のチェックのなかに選択肢を含む初めてのケースです。
各選択肢にはタグによって注釈（ここでは：nameと：id）が付けられています。
これらのタグは、`conform`および他のspec関数から返されたデータを理解、または充実させる際に使用するブランチ名を与えます

 `or` の条件を満たしたときにspecは、そのタグ名と一致した実際の値が入ったvectorを返します。

```clojure
(s/conform ::name-or-id "abc")
;;=> [:name "abc"]
(s/conform ::name-or-id 100)
;;=> [:id 100]
```

`string?` `number?` `keyword?` などのように、インスタンスの型をチェックする述語の多くは、nilを有効な値として許可していません。
そのため、nilを有効な値に含めたい場合は `nilable` 関数を使ってspecを作成します。

```clojure
(s/valid? string? nil)
;;=> false
(s/valid? (s/nilable string?) nil)
;;=> true
```

# Explain
`explain` は、「なぜ値がspecを満たさないのか」を（*out* へ）レポートしてくれる、もう一つの高度なオペレーションです。
それでは前述の例を使って、`explain` がspecと適合しなかった場合に何と言ってくれるのかを見てみましょう。

```clojure
(s/explain ::suit 42)
;; val: 42 fails spec: ::suit predicate: #{:spade :heart :diamond :club}
(s/explain ::big-even 5)
;; val: 5 fails spec: ::big-even predicate: even?
(s/explain ::name-or-id :foo)
;; val: :foo fails spec: ::name-or-id at: [:name] predicate: string?
;; val: :foo fails spec: ::name-or-id at: [:id] predicate: int?
```

最後の出力結果をより詳しく調べてみましょう。
最初に、2つのエラーが報告されていることに注意してください。
specは可能な選択肢の全てを評価し、すべてのパスでのエラーをレポートします。

エラー内容の詳細：
* val - specに適合しなかった入力値
* spec - 評価の対象となったspec
* at - エラーが発生したスペック内の位置を示すパス（vectorのキーワード）。パス内のタグは、spec内で任意の箇所にタグ付けされた部分と対応しています（`or` や `alt` の選択肢や、`cat`の一部、mapのkeysなど）。
* predicate - 条件を満たすことができなかった述語
* in - 条件を満たすことができなかった値へのパス（データがネストされていた場合に）。この例ではトップレベルの値で不適合で空のパスとなるため、省略されている。

最初のエラーでは、`::name-or-id` という名のspecの `:name` というパスにおいて、`:foo` が `string?` を満たさなかったということが分かります。
二つ目のエラーも似ていますが、`:id` というパスにおいて失敗しています。`:foo` はキーワードですので、どちらも一致しません。

加えて、`explain-str` を使用してエラーメッセージを文字列または `explain-data` として、エラーをデータとして受け取ることができます。

```clojure
(s/explain-data ::name-or-id :foo)
;;=> #:clojure.spec.alpha{
;;     :problems ({:path [:name],
;;                 :pred string?,
;;                 :val :foo,
;;                 :via [:spec.examples.guide/name-or-id],
;;                 :in []}
;;                {:path [:id],
;;                 :pred int?,
;;                 :val :foo,
;;                 :via [:spec.examples.guide/name-or-id],
;;                 :in []})}
```

>この結果は1.9.0-alpha8で追加された新しい名前空間、マップリテラル構文も説明しています。
>Mapには、Map内すべてのキーにデフォルトの名前空間を指定するため、接頭辞として `#:` か `#::`（こちらは自動解決用） が付きます。
>この例では、{:clojure.spec.alpha/problems …​}に相当します。


# Entity Maps
Clojureプログラムは、データマップを順に渡すことに大変依存しています。
他のライブラリにおいてよく取られるアプローチは、各エンティティの型や、そこに含まれるキーとキーを組み合わせ、そして値の構造の説明です。
エンティティ（Map）のスコープ内の属性（key+value）を定義するのではなく、specは個々の属性に意味を持たせた、それらを集めてセマンティックなsetを使っているMapへまとめます。このアプローチは、ライブラリとアプリケーションにおける属性レベルでの、セマンティクスの割り当てや共有を可能にします。

例えば、ほとんどのRingミドルウェア関数は、リクエストやレスポンスのmapを修飾されていないキーで変更します。
しかし、各ミドルウェアは代わりに、登録されたセマンティクスを持つ名前空間のキーを使うことができます。
そのキーは適合性のチェック、より素晴らしい共同制作の機会を増やすような、一貫性の高いシステム構築を可能にしてくれます。

specにおけるエンティティマップは、`keys` を使って定義します。

```clojure
(def email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$")
(s/def ::email-type (s/and string? #(re-matches email-regex %)))

(s/def ::acctid int?)
(s/def ::first-name string?)
(s/def ::last-name string?)
(s/def ::email ::email-type)

(s/def ::person (s/keys :req [::first-name ::last-name ::email]
                        :opt [::phone]))
```

ここでは、必須のキーとして `::first-name`、 `::last-name`、 `::email`を持ち、任意のキーとして `::phone` を持つ、`::person` というspecの登録を行っています。mapのspecでは、属性が必須なのか任意なのかだけを指定し、値のspecは決して指定しません。

map上での適合性チェックの際は、「必須の属性が含まれているのか」「登録された何れのキーも、適合する値を保持しているか」の2点が検査されます。
任意の属性が役に立つケースについては、後で説明します。
また、`:req` と `:opt` キーに載っている属性だけではなく、「全ての」属性は `keys` を通じてチェックされる点に注意してください。
つまり、そのままの `(s/keys)` は有効で、どのキーが必須か任意かのチェックに関係なく、map内すべての属性をチェックするということです。


```clojure
(s/valid? ::person
  {::first-name "Elon"
   ::last-name "Musk"
   ::email "elon@example.com"})
;;=> true

;; Fails required key check
(s/explain ::person
  {::first-name "Elon"})
;; val: #:my.domain{:first-name "Elon"} fails spec: :my.domain/person
;;  predicate: (contains? % :my.domain/last-name)
;; val: #:my.domain{:first-name "Elon"} fails spec: :my.domain/person
;;  predicate: (contains? % :my.domain/email)


;; Fails attribute conformance
(s/explain ::person
  {::first-name "Elon"
   ::last-name "Musk"
   ::email "n/a"})
;; In: [:my.domain/email] val: "n/a" fails spec: :my.domain/email-type
;;   at: [:my.domain/email] predicate: (re-matches email-regex %)
```

ここで少し、最後の例についての、explainのエラー内容を調べてみましょう。

* in - 評価が失敗した値へのデータ内のパス。ここでは、personインスタンス内のキー。
* val - 失敗した値。ここでは`"n/a"`。
* spec - 失敗したspec。ここでは、`:my.domain/email`。
* at - 失敗した値が配置された、spec内でのパス。
* predicate - 失敗した述語。ここでは`(re-matches email-regex %)`。

既存のClojureコードの多くは、名前空間のキーを持つマップを使用していません。そのため`keys` では、非修飾のキーにおける必須と任意についても `:req-un` と `:opt-un` を使って指定することができます。
これらのバリアントは、仕様を検索する際に使われる、名前空間のキーを指定します。しかし、マップはキーの非修飾バージョンのみをチェックします。

下記のpersonマップは非修飾のキーを使っていますが、名前空間にあるspecに適合するかのチェックを行うことができます。
このことについて、以前登録したspecを使って考察してみましょう。

```clojure
(s/def :unq/person
  (s/keys :req-un [::first-name ::last-name ::email]
          :opt-un [::phone]))

(s/conform :unq/person
  {:first-name "Elon"
   :last-name "Musk"
   :email "elon@example.com"})
;;=> {:first-name "Elon", :last-name "Musk", :email "elon@example.com"}

(s/explain :unq/person
  {:first-name "Elon"
   :last-name "Musk"
   :email "n/a"})
;; In: [:email] val: "n/a" fails spec: :my.domain/email-type at: [:email]
;;   predicate: (re-matches email-regex %)

(s/explain :unq/person
  {:first-name "Elon"})
;; val: {:first-name "Elon"} fails spec: :unq/person
;;  predicate: (contains? % :last-name)
;; val: {:first-name "Elon"} fails spec: :unq/person
;;  predicate: (contains? % :email)
```

レコードの属性を検証するために、非修飾のキーを使うこともできます。

```clojure
(defrecord Person [first-name last-name email phone])

(s/explain :unq/person
           (->Person "Elon" nil nil nil))
;; In: [:last-name] val: nil fails spec: :my.domain/last-name at: [:last-name] predicate: string?
;; In: [:email] val: nil fails spec: :my.domain/email-type at: [:email] predicate: string?

(s/conform :unq/person
  (->Person "Elon" "Musk" "elon@example.com" nil))
;;=> #my.domain.Person{:first-name "Elon", :last-name "Musk",
;;=>                   :email "elon@example.com", :phone nil}
```

任意の連続的なデータ構造内で、キーワードのキーと値が渡ってくる場合にClojureでよく起こる出来事は、"keyword args"の使用です。
specはこのようなときのために特別なサポート、正規表現オプションである `keys*` を提供しています。
`keys*` は `keys` と同じシンタックスやセマンティクスを持っていますが、連続的な正規表現構造の中に埋め込むこともできます。

```clojure
(s/def ::port number?)
(s/def ::host string?)
(s/def ::id keyword?)
(s/def ::server (s/keys* :req [::id ::host] :opt [::port]))
(s/conform ::server [::id :s1 ::host "example.com" ::port 5555])
;;=> {:my.domain/id :s1, :my.domain/host "example.com", :my.domain/port 5555}
```

部分的にエンティティマップを宣言しておくと便利な場合もあります。
エンティティマップの要件のソースが様々であったり、共通するキーのセットとバリアント固有の部分がある場合などです。
`s/merge` specは、複数の `s/keys` specを組み合わせ、要件を一つのspecにまとめるのに使用できます。
例として、動物共通の属性と犬固有のいくつかの属性を定義する `keys` specを考えてみましょう。
犬エンティティそれ自体は、その2つの属性のセットをマージしたものとして記述することができます。

```clojure
(s/def :animal/kind string?)
(s/def :animal/says string?)
(s/def :animal/common (s/keys :req [:animal/kind :animal/says]))
(s/def :dog/tail? boolean?)
(s/def :dog/breed string?)
(s/def :animal/dog (s/merge :animal/common
                            (s/keys :req [:dog/tail? :dog/breed])))
(s/valid? :animal/dog
  {:animal/kind "dog"
   :animal/says "woof"
   :dog/tail? true
   :dog/breed "retriever"})
;;=> true
```





〜　続く（時間を見つけて更新していきます）〜

# 参考サイト
[本家](https://clojure.org/guides/spec)