ClojureScript + Reagent で 3目並べ (tic tac toe)

React チュートリアルのお題 3目並べを ClojureScript + Reagent で書いてみた。

Reagent とは

ClojureScript から React を利用しやすくするライブラリで、公式のサンプル をざっとみてもらうと、想像しやすいかと思います。

コンポーネントは React のように jsx ではなく、

[:h2.subtitle "Title"]

のように書きます。

これは Clojure のHTML出力用ライブラリ hiccup の記法で、「HTMLってS式で表現できるじゃん」という Lisper 的な発想から出てきた記法だと思います。Clojure だけではなく、 おそらく Common Lisp/Scheme にも、類似のライブラリがあるはずです。

デモページ

https://f6o.github.io/tick/

React チュートリアルに示されている完成サンプルと異なる点があります。

  • ステータスの表示

    引き分けと勝者を表示する

  • 履歴の機能

    履歴を辿ると、その履歴の前のものが全てが消えてしまう

はまったところ

データが更新されたら、まるっとデータが消される罠

(let [data (new-data)]
    [:div.game-info
        この中で data を使う])

というようにコンポーネントの中身を書いていたら、このコンポーネントが更新されたときに、データも空になってしまうという、自分の間抜けな実装に悩まされました。

(new-data) を引数としてコンポーネントに渡す、もしくは defonce でグローバルな変数として持つという形にすれば解決できます。

データ構造の選択について

初めは Map の中に atom を入れる実装にしていました。

{
  :board [(reagent.core/atom "")
          (reagent.core/atom "") ...]
  :status (reagent/atom :in-play)
  ...
}

ただ、先駆者の実装をみてみると、

(reagent.core/atom
    {
        :board ["" ""...]
        :status :in-play
        ...
    })

のように Map 全体を atom としていて、そうすると、値の参照や更新が書きやすくなりました。

具体的にいえば、参照の時は deref が減り、更新の時は (swap! atomic-data assoc :key "SOME VALUE") というパターンに統合されました。ところどころ、reset! はありますが。

結論は、今回の3目並べだと、全体的に atom としても良いと思います。

ただ、ある関数はこの値だけを参照・更新して、他の関数は別の値だけ参照・更新する、というような場合だと、全体的に atom としてしまうのは無駄な気がします。

また、そういう場合だと「なんで 1つのMap に詰め込んでいるんだ!」とデータ構造警察が訪ねてきそうです。

環境づくり

最後に、環境設定についても触れておきます。

1. とりあえず leiningen をインストール

https://leiningen.org

2. プロジェクトの作成

プロジェクトのスケルトンを作ってくれるので、あまり考えずにスタートできるのが、魅力的です。今回バックエンドは不要だったので、フロントエンドのみのスケルトンを使いました。

https://github.com/reagent-project/reagent-frontend-template

$ lein new reagent-frontend <APPNAME>
$ cd <APPNAME>
$ lein figwheel
...
...
あとはコーディングを頑張る

figwheel は ClojureScript の高機能ビルドツールです。

開発サーバをたててくれたり、デバッガ + オートローダ付きだったり、ブラウザ上のアプリとつながった REPL を利用できたりと、多機能過ぎます。

何が内部で起こっているのか、何を使っているのか、など気になるので、lein/project.clj のお作法についても、知っておく必要があります。

3. わからなかったら、Clojurescript のチートシートを参考に

常時タブに開いておきました: http://cljs.info/cheatsheet/

次は何をしようか。

面白い記事があるので、ClojureScript でブラウザ上で動くdiff を実装してみたいと思います。