DatascriptでDatomicのチュートリアルをなぞってみる

Clojure界では有名な[Datomic](http://www.datomic.com/)というプロダクトとAPIの互換性があるOSSライブラリである
[Datascript](https://github.com/tonsky/datascript)に興味を持ったので触ってみた。

題材としてDatomicの[Getting Started](http://docs.datomic.com/getting-started/transact-schema.html)をなぞってみることにする。

## 1. インストール

`[datascript "0.16.1"]` を `:dependencies`に追加する

## 2. コネクションを作る

```clj

(require '[datascript.core :as dc])
(def con (dc/create-conn))

```

## 3. Schemaをトランザクトする

この例では映画をモデリングする。
映画にはリリース年、ジャンル、タイトルの属性(attribute)があるものとする。

attributeの定義には以下が必要となる。
- `:db/ident` attributeのユニークな名前
- `:db/valueType` attributeが保持するデータの型 
- `:db/cardinality` attributeのデータが単一の値か、もしくはコレクションを保持するか

また、任意の :db/doc でattributeのdocstringを定義することが推奨される。

attributeはデータのマップで定義される。

タイトルは以下のように定義する

```clj

{:db/ident :movie/title
 :db/valueType :db.type/string
 :db/cardinality :db.cardinality/one
 :db/doc "The title of the movie"}
```

ジャンルは以下のように

```clj

{:db/ident :movie/genre
 :db/valueType :db.type/string
 :db/cardinality :db.cardinality/one
 :db/doc "The genre of the movie"}
```

リリース年は整数であるため `:db/valueType` を :db.type/longに変更して以下のようにして定義する

```clj

{:db/ident :movie/release-year
 :db/valueType :db.type/long
 :db/cardinality :db.cardinality/one
 :db/doc "The year the movie was released in theaters"}
```

スキーマはひとつづつトランザクトすることも可能だが、ここではまとめてトランザクトすることにする。

```clj
(dc/transact con [{:db/ident :movie/title
                   :db/valueType :db.type/string
                   :db/cardinality :db.cardinality/one
                   :db/doc "The title of the movie"}

                  {:db/ident :movie/genre
                   :db/valueType :db.type/string
                   :db/cardinality :db.cardinality/one
                   :db/doc "The genre of the movie"}

                  {:db/ident :movie/release-year
                   :db/valueType :db.type/long
                   :db/cardinality :db.cardinality/one
                   :db/doc "The year the movie was released in theaters"}])

```

conを評価してみると何かが入っているのがわかる。

```clj
user> con
#<Atom@4afb7219: 
{1 :db/cardinality,
 1 :db/doc,
 1 :db/ident,
 1 :db/valueType,
 2 :db/cardinality,
 2 :db/doc,
 2 :db/ident,
 2 :db/valueType,
 3 :db/cardinality,
 3 :db/doc,
 3 :db/ident,
 3 :db/valueType}

```

## 4. データをトランザクトしてみる

以下のようなデータを定義する

```clj

(def first-movies [{:movie/title "The Goonies"
                    :movie/genre "action/adventure"
                    :movie/release-year 1985}
                   {:movie/title "Commando"
                    :movie/genre "action/adventure"
                    :movie/release-year 1985}
                   {:movie/title "Repo Man"
                    :movie/genre "punk dystopia"
                    :movie/release-year 1984}])

```

スキーマと同じようにトランザクトしてDBに追加する

```clj
(dc/transact con first-movies)
```

conを再度評価すると何かが増えていることがわかる。

```clj
user> con
#<Atom@4afb7219: 
  {1 :db/cardinality,
   1 :db/doc,
   1 :db/ident,
   1 :db/valueType,
   2 :db/cardinality,
   2 :db/doc,
   2 :db/ident,
   2 :db/valueType,
   3 :db/cardinality,
   3 :db/doc,
   3 :db/ident,
   3 :db/valueType,
   4 :movie/genre,
   4 :movie/release-year,
   4 :movie/title,
   5 :movie/genre,
   5 :movie/release-year,
   5 :movie/title,
   6 :movie/genre,
   6 :movie/release-year,
   6 :movie/title}
```


## 5. クエリを投げてみる

まずコネクションから`db`なるものを作る。

```clj
(def db (dc/db con))
```

このdbはイミュータブルで、同じクエリに対して同じ結果が返ってくることが保証されている(注目ポイントらしい)。

DBから値を取得する方法は2種類ある:

- `query` Datalogという宣言的なクエリ言語でクエリを投げる
- `pull` エンテティに関する情報を宣言的に階層化してセレクトする

`:movie/title` のattributeを持つ物をdbの中から探してみる

```clj
(dc/q '[:find ?e
        :where [?e :movie/title]]
      db)
=>#{[4] [6] [5]}
```

エンテティIDのかわりに映画のタイトルを抜き出すには以下のようにする

```clj
(dc/q '[:find ?movie-title 
        :where [_ :movie/title ?movie-title]]
      db)
=> #{["The Goonies"] ["Commando"] ["Repo Man"]}
```

リリース年が1985の映画のタイトルは以下のように取得する

```clj
(dc/q '[:find ?title 
        :where [?e :movie/title ?title] 
               [?e :movie/release-year 1985]]
      db)
=>#{["The Goonies"] ["Commando"]}

```

リリース年が1985年の映画の全てのattributeの情報は以下の様にして取得することができる

```clj
(dc/q '[:find ?title ?year ?genre 
        :where [?e :movie/title ?title] 
               [?e :movie/release-year ?year] 
               [?e :movie/genre ?genre] 
               [?e :movie/release-year 1985]]
      db)
=> #{["The Goonies" 1985 "action/adventure"]
     ["Commando" 1985 "action/adventure"]}
```

pullは以下の様に使う

```clj

(dc/pull db 
         '[*] ;;selector
         4 ;; entity id
         )
=>{:db/id 4,
   :movie/genre "action/adventure",
   :movie/release-year 1985,
   :movie/title "The Goonies"}
```

## 6. TODO
- 用語の定義
- Datomic/Datascriptの何が嬉しいのかを実感する
- pullの詳細
- 複雑な応用例