備忘録 blog

Docker/Machine Learning/Linux

three.jsでテキストの向きをカメラに追従させる

Three.js で三次元の可視化を実装するときに、カメラ移動を実装して、様々な角度からその三次元データを見られるようにしたいと考えることがある。このときに必要なマウスドラッグやマウスホイールによるカメラ移動は、OrbitControls.js を使うと苦も無く実装できる。 このとき、Three.js で三次元空間上にテキストを配置してあったとする。その状態で視点を変えると、デフォルトの設定ではテキストの向きが配置時の向きのまま固定されてしまっているので、テキストに対して垂直方向に視点を変えると、そのテキストが読めなくなってしまう。 そのため、カメラの位置に追従し、テキストが常にユーザー側に向いている実装に変更したいと考えた。このとき、試したことや調べたことをまとめる。

うまくいった方法

↑のような実装をしたい時の検索ワードは、face camera, face user, look at camera などのキーワードを用いると良いというのが、調べながら分かってきた。

text.quaternion.copy( camera.quaternion );

オブジェクトの向きは、three.jsなどの各種3Dツールでは、クォータニオンによって定義されている。この↑のコードをオブジェクトの定義時ではなくて、render 時に書くことで、カメラの回転角と、回転させたいオブジェクトの回転角を同期させることができるようになった。

試してうまくいかなかったこと

検索して最初に出てくる方法は、lookAtを使う方法である。これは、 OrbitControls.js を使ったときは利用できないので、うまくいかない。

text.lookAt( camera );

また奥の手として、テキストを画像として貼り付けてしまえば、カメラの向きに画像の向きが固定されるということを利用して、いったんテキストをSVGに吐いてから*1、それをSpriteとして貼り付けるということも考えたが、これもうまく動かなかった。

$ gem install text2svg
    var map = THREE.ImageUtils.loadTexture( "fonts/text.svg" );
    var material = new THREE.SpriteMaterial( { map: map, color: 0xffffff, fog: true } );
    var titleX = new THREE.Sprite( material );
    titleX.position.x = xScale(vpts.xMin) - 6,
    titleX.position.y = 2;
    scatterPlot.add( titleX );

    var geometry2 = new THREE.Geometry();
    var vertex2 = new THREE.Vector3(xScale(vpts.xMin) - 6, 2, 0);

    geometry2.vertices.push( vertex2 );

    var material2 = new THREE.PointCloudMaterial( { size: 100, sizeAttenuation: false, map: map, alphaTest: 0.5, transparent: true } );

    var titleX = new THREE.PointCloud( geometry2, material2 );

    scatterPlot.add( titleX );

いまのtextはTHREE.Textureで作っていたが、THREE.Spriteで作るようにすると、THREE.Spriteはカメラに対して常に正面を向くオブジェクトを生成することができるので、こちらの方が良かったかもしれない。

ics.media

参考文献

stackoverflow.com

stackoverflow.com

stackoverflow.com

*1:この方法だと、静的なテキストしか配置できないことになってしまうという欠点があるが。

現時点での好きなLinux環境

自分の気に入っている、Linux環境上でのソフトウェア群をまとめてみた。

Desktop Environment

KDE

重厚長大だが、充実した機能とカスタマイズ性が、どのKDEのバージョンでも維持されており、それがKDEの魅力でもある。Qt製で、Qtのバージョンアップに従ってKDEもバージョンアップを重ねており、現行バージョンはKDE Plasma 5である。KDE 4の頃はめちゃくちゃカスタマイズしていたが、KDE Plasma 5になってから面倒になってデフォルト設定のまま使うようになってしまった*1

Xmonad

マシンスペックによってはKDEはさすがに重すぎるので、計量デスクトップ環境を導入したくなることがある。そのときに選んだのはXmonadだった。キーバインドを覚えなければならないので、触れていないと使い方を完全に忘れてしまうという欠点はある。その一方で、カスタマイズ性が高く、デスクトップを自在に構成することができるのが、大きな利点であり、マウスなどなくとも大抵の操作ができることに感動する。

Linux Distributions - Arch Linux

Linux Distributionの違いを規定する大きな要素の1つにPackage Managerがあると考えており、Package Managerのあり方に、そのディストリビューションのコンセプトが反映されているというように感じる。

Package Manager

apt-get, aptitude, yum, dnf, zypper, portage, pacman, ...., の様々なツールがあるが、pacmanの、公式リポジトリから導入できるパッケージはバイナリとして導入できて、そうでないソフトウェアは、ビルドファイルがAURと呼ばれるコミュニティベースのリポジトリに共有されており、そこからダウンロードする場合は原則として手元でビルドするという仕組みになっているのが、portageのように全てのソフトウェアをビルドするというほど時間がかからず、一方で、パッケージマネージャに管理されていないソフトウェアをインストールするのがそれほど面倒ではない*2ことが、一番気に入っている理由かもしれない。

Shell - fish

ssh環境ではだいたいの場合で、bashがデフォルトなのでそれを使っているが、zsh + oh-my-zshに比べてfishのほうがfish_configによる設定が容易だったので、fishを利用している。fishはカスタマイズの容易性ももちろんのこと、補完が過去の入力履歴をもとに常に先取り的に行われるというのが便利である。bashでもしばしばback-i-searchを使うことが多いので、私の場合のだいたいのコマンド操作は、過去のコマンド入力の繰り返しであるか、その改変にすぎないということが肌感覚で分かったので、fishに移行したときはその便利さに驚いた。

Editor - Emacs

最初のあいだは、頑張ってEmacs Lispで設定ファイルを記述していたが、最近それに倦んでいるので、Spacemacsを使う。

.spacemacs に設定を記述する。dotspacemacs-configuration-layersは今はこんな感じで書いている。

   dotspacemacs-configuration-layers
   '(
     yaml
     (auto-completion haskell :variables  haskell-completion-backend 'ghc-mod)
     ;; better-defaults
     emacs-lisp
     git
     github
     tmux
     command-log
     org
     spacemacs-layouts
     (ibuffer :variables ibuffer-group-buffers-by 'projects)
     semantic
     cscope
     gtags
     latex

     (ruby :variables
           ruby-version-manager 'rbenv
           ruby-enable-enh-ruby-mode t)
     go
     (haskell :variables
              ;;haskell-completion-backend 'intero
              haskell-process-type 'stack-ghci
              haskell-enable-hindent-style "johan-tibell")
     rust
     scheme
     python
     markdown
     javascript
     react
     c-c++
     html
     docker
     typescript
     dash
     ansible
     sql
     osx
      (shell :variables
             shell-default-height 30
             shell-default-position 'bottom)
     ;; spell-checking
     syntax-checking
     version-control
     )

Other

あとはだいたいこのあたりを使っているが、あとはほとんど設定せずデフォルト状態で使っているので、これ以上細かく述べない。

*1:ただし、メニューバーは上にあるのが好みである

*2:コミュニティが発達しているので、だいたいのソフトウェアがAURで公開されている

Google BigQuery の SQL文tips

Google BigQueryとはおおざっぱに言うと、大量のテーブルデータに対して高速にSQLを実行できるフルマネージドサービスである。主にアクセスログや、テーブルデータのように、十分に大きいデータソースを持っている企業にとっては安価で、すぐにSQLを実行することができるため、導入している企業も多い*1。 そのため、企業のログデータ解析をするとなると、まず選択肢に上がるのはこれか、Treasure Data などになるのではないだろか。

BigQueryを実行するにあたっては、SQL文を記述する必要がある。単純にテーブルデータを呼び出すだけの利用目的であれば、RDBMS バックエンドのjsonサーバーを実装するときに書くような、Web エンジニアが普段書くSELECT文で十分なケースも多い。しかし実際には、テーブルを跨いだ集計が必要だったりすると、クエリは長くなってしまい、平気で数百行の SQLになってしまうこともしばしばあある。こうしたSQL文は、初見では読み解くのが難しかったり、フルスクラッチで一から組み立てるのが難しかったりする。

ここでは、StandardSQL の文法のもとで、筆者がBigQueryでSQL文を書く中で気づいた、お役立ちtipsをいくつか紹介したい。

具体例

1. with句を利用してサブクエリをシーケンシャルに記述する

例えば、2つのテーブルA,Bがあって、それぞれに対して絞り込んだものを最終的にJOINしたい、というような場合では、

  • テーブルAを絞り込んでできたA'
  • テーブルBを絞り込んでできたB'
  • A'とB'をジョインしてできたC

のような手順でクエリを構成したい。こうしたものを記述するときに、よくあるのはジョインしたいテーブルの情報を、サブクエリで書き下す場合である。

SELECT * FROM `log` INNER JOIN ( SELECT * FROM `users` WHERE users.id < 1000 ) AS master_users ON master_users.id = log.user_id ;

だが、これが複雑に入り組んでくると、クエリの全貌をとらえるのが難しくなる。そこで、サブクエリの変種であるWITH句を利用して、

WITH master AS (
  SELECT * FROM `log` 
  WHERE log.id < 1000
), master_users AS (
  SELECT * FROM `users` 
  WHERE users.id < 1000
) 
SELECT * FROM `log` 
INNER JOIN master_users ON master_users.id = log.user_id ;

と書くと、WITH句によってフローをシーケンシャルに記述することができるので、上から順に読んでいくとどういう流れで処理しようとしているのか、より分かりやすく表現することができる(はず)。

2. 何度も使い回したい数値を関数として使い回したい

SQL文内で関数を定義することもできる。

CREATE TEMPORARY FUNCTION max_user_count() AS ('1000');

これは例えば、先ほどのwith句を利用するような長大なクエリの中で、共通の条件で絞り込みたいといった場合に応用ができる。

3. 1ずらしの連続値が入っているカラムを、joinによってeach_consする

たとえばこんなテーブルを考えてみよう。

id group_id seq_id
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2

このテーブルでは、全体に対してidが付与されているほか、複数のレコードにわたって共通のgroup_idが付与されており、そのgroup_id内でシーケンシャルに増加するidが割り振られている。このとき、同じgroup_idの中で、連続するseq_idのあいだで、どのような変化が起きているか、という1次のマルコフ性を考慮した解析を行いたいということがある。ruby でいうところの、each_cons に相当するような処理を、BigQueryで記述したいということである。

このとき、seq_idを1ずらしたテーブルを作って、seq_idでjoinすれば良い。具体的には、join前のテーブルをwith句で取ってくる時に、seq_idをデクリメント(ないしインクリメント)して取ってくる。そして、その後にseq_idと、group_idが一致するものをマージするというSQL文になる。

WITH master_1 AS (
  SELECT
    id,
    group_id
    seq_id
    target
  FROM
    `sample.log`
, master_2 AS (
  SELECT
    id,
    group_id
    seq_id - 1 as seq_id, 
    target
  FROM
    `sample.log`
) SELECT DISTINCT 
   master_1.group_id
   master_1.seq_id
   master_1.target AS target_1
   master_2.target AS target_2
  FROM
    master_1
  LEFT JOIN
    master_2
  ON
    master_1.group_id = master_2.group_id AND master_1.seq_id = master2_.seq_id

たとえば上記のようにすることで、target カラムが、seq_idの前後でどう変化したかをみることができるようになる。

4. case whenを利用して一致したら1、不一致なら0を返す

SELECT CASE WHEN type = 'User' THEN 1 ELSE 0 FROM `users`

これを応用して、一致する要素が何件あるかを集計することも可能だ。

SELECT SUM(CASE WHEN type = 'User' THEN 1 ELSE 0)  FROM `users`

5. "&=" で結合されたパラメータを別々の行に分ける

URLをカラムに直接格納するとき、しばしば、/query?q=param1&q=param2 のように、アンドで結んだパラメータが入っていることがある。 これを、それぞれのクエリ文字列に分けて取り出して解析をしたいというとき、

WITH log AS (
  SELECT * ,
  REGEXP_EXTRACT_ALL(parameters, r'(?:^|&)q=(\d+)') as params
  FROM `log`),
log_param AS (SELECT
  *
FROM  log
CROSS JOIN
  UNNEST(log.params) AS param)

上記のように書くことで、param というカラムにもともと複数入っていた複数のパラメータが展開されて、それぞれ1つずつパラメータが別々のレコードに格納されるようになる。これを下流の解析で用いることで、パラメータの文字列ごとの集計などが可能になる。

*1:学生の頃には大金でとても払えないし、そもそもBigQueryが必要とされるほどのデータを集めること自体が難しいので、全く使うことはなかったが。

3Dで使われるオイラー角とクォータニオンとは何か?

tl;dr

3次元上の回転について調べたのでまとめる備忘録。

復習: 2次元の場合はどうだったか

2次元座標上で回転をどう定義するか、ということについては、ここでは詳説しない。

  1. 回転行列
  2. オイラー
  3. 複素数

これらの表現が、3次元上ではどうなるのかについて考える。

3次元上の回転行列

回転を表現するだけであれば3x3行列で十分すぎるはずである。9セルあるので、自由度が9であるように思われがちだが、それぞれのベクトルが単位ベクトルであり、直交しているという制約があるので、実質的に自由度は3である。このため、他の表現よりメモリを必要とすることと、無効となる表現が多いというのが欠点としてあげられる。

しばしば回転は、平行移動などと共通に扱いたいとされる。しかし、3x3行列のままでは、原点に対してはどのような3x3行列との積をとっても原点のままになってしまう。平行移動では、アフィン変換になるので、そこで、  {p = (x, y, z)} に対して斉次座標を利用してもう1次元追加した、  {p = (x, y, z, 1)} を考え、これに対する4x4行列による写像を、回転と平行移動を同時に表現できるフォーマットとして利用することがある。このとき、4x4行列の左上の3x3成分だけを回転行列として扱うことができる。

3次元上のオイラー

2次元におけるx,y軸の拡張であるオイラー角は、一般に

{ \displaystyle
(\theta, \phi, \psi)
}

として表現される。

これは、例えばヨー、ピッチ、ロールの3軸としたとき、それらに対して同時にその角度回転する、というわけではない。最初にある軸に対して回転し、その次は、回転した後の向きに対して改めてx,y,z軸を定義し、次の軸で回転し、最後にまた回転後の向きに対して軸を再定義して回転する、という順番で回転したものの合成として表現される。つまり、この回転の順序のやり方によって、最初の回転軸と最後の回転軸が同じになるパターン*1を含めて12パターンのオイラー角を考えることができる。実際は1つの順序(ZYXやYXZの順など)に決め打ちするものが多い。一方で、回転する前の初期状態のxyz軸に対しての回転を行うという流派もあるようだが、ここではそれは考慮しない。

これによって、3次元上の回転は3つの自由度で表現することができるはずである。利点として、何より我々にとってこの表現方法は直感的であるということと、それぞれの軸における回転は何度回ったとしても表現できるので、180°より大きい回転角を表現することも可能であるということが挙げられる。

しかし実際にこの系を用いると、ジンバルロックとよばれる問題が生じる。これは何か。ジンバルと呼ばれる、三次元のジャイロを考えてみよう。最初の状態では、XYZ軸に対応するそれぞれの軸は、どの2つをとっても垂直になるように交わっている。回転によって、軸間の角度がかわると、場合によっては最も外側の軸と、最も内側の軸が水平になってしまうことがある。このとき、2方向にしか動かすことができなくなってしまう*2

回転を実行することと、その回転を書き下すことの間には、本来は両矢印を引くことができて欲しいのだが、その矢印の両方に問題が残っている。それは、ある回転が与えられたとき、その角度をオイラー角で書き下そうとすると、複数の角度の組み合わせが考えられてしまうこと。これについては、角度の値域を固定することで解決することができる。逆に、ある状態に対して、ある回転を実行しようとするときに、状態によっては、先ほどのように2つの軸が同じ向きになってしまうと自由度が2になってしまい、自由度がない向きに直接回転できなくなってしまうことがある。この後者がジンバルロックであり、これは簡単に回避することが難しい。

この性質から、ある2点のあいだの回転を「補間」することが難しくなってしまう。単純に、2点の角度の相加平均をとったとしても、その補間点の前後での角速度が変わってしまったり、

そこで、実は3つの数字で表現するのではなく、4つの数字で表現すれば、仮に2つの数字が従属関係となり、自由度が1つ減ったとしてもなお、3軸の向きの回転を表現できるということが考えられる。これが、次に関係してくる話題となる。

3次元上の複素表現: 四元数

2次元における複素数の拡張であるから、3次元の表現において、実部が1つ、虚部が2つの三元数というものが存在すれば良いのではないか、と直感的に思うだろう。

{ \displaystyle
t = a + bi + cj (a,b,c \in \mathbb{R}, i^{2} = j^{2} = -1)
}

このとき、2つの三元数の積を考えると、この積には  {ij} となる項が含まれるが、それは元の定義には存在しない項である。つまり、この定義では、積について閉じていないことがわかる。そこで、新しい虚数軸として  {k} を導入する。

{ \displaystyle
q = a + bi + cj + dk (a,b,c,d \in \mathbb{R}, i^{2} = j^{2} = k^{2} = ijk = -1)
}

これで定義された  {q}四元数クォータニオン)と呼び、複素数に期待される種々の性質を満たしていることが計算によって示される。 このクォータニオンを利用することの利点は、先ほどのオイラー角のような特異点が存在しないので、なめらかに2点間の回転移動を記述することができる。また回転行列に比べて、演算の回数が減らせるというのも大きな利点だ。行列は先述の通り、自由度に比べて扱うセルの個数が大きいので、和や積など様々な演算で、スカラー同士の積和の演算回数も多くなってしまう。

しかしながら、クォータニオンを利用する際にも欠点は存在し、オイラー角のように270°の回転などを表現することはできず、最短経路での回転として一意に表現されてしまう。また、最大の欠点は、その値をみても、どういう回転の処理に対応するのかよく分からないというところである。

Blender のリグでオイラー角とクォータニオンのどちらを使うべきか

参考文献

qiita.com

www.opengl-tutorial.org

qiita.com

el-ement.com

*1:XZZのように

*2:軸がない方向に回転させるすべがないためである

RailsとReactを統合する方法を考える

なぜReactを導入したくなるか

現代のWebアプリケーションの発ではしばしば、リッチなユーザエクスペリエンスを提供するためなど様々な理由で、クライアント側に(ウェブブラウザ上に)状態を持っておきたいという状況があります。例えばチェックボックスにチェックが入っているかどうかで、他のDOM要素の内容が変わったり、モーダルが閉じているか開いているかを管理したり、などなど……。もちろん技術選択として、こうした操作が全く必要ない実装にすることも可能です。しかし、ボタンをトグルしたら画面上に何らかの変化があることをユーザが期待するというように、今やユーザビリティと、動的なウェブページは密接な関連があります。

さてこのとき、ユーザーの操作に応じて、HTMLの階層構造に含まれる1つ1つのタグで囲まれる要素を追加、削除、変更などする必要があります。こうした構造のことをDOM(Document Object Model)と呼びますが、クライアント側でDOMを操作するために、JavaScriptを書くことが事実上不可避となります。しかしながら素のJavaScriptを書くのはウェブブラウザ間の互換性の観点から、かなり大変です。例えば、このような場合に困りました。

event.target.matches('.heart')

これは、選択したDOM要素が、heartクラスかどうかを判定したいコードです。

developer.mozilla.org

これは、IEでは動作しません。なぜならばIEでは、matchesは非標準の名前 msMatchesSelectorというAPIで実装されているからです。またIEでなくとも、FirefoxChromeの古いバージョンのブラウザを利用しているユーザがいれば、このコードを正しく解釈できないかもしれません。これに対処するためには、古いバージョンのブラウザでも動作するように、互換性に配慮した関数定義に上書きするコードを導入する必要があります。これをpolyfillと呼びます。上記の例では、以下のコードがそれに対応します。

if (!Element.prototype.matches) {
    Element.prototype.matches = 
        Element.prototype.matchesSelector || 
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector || 
        Element.prototype.oMatchesSelector || 
        Element.prototype.webkitMatchesSelector ||
        function(s) {
            var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                i = matches.length;
            while (--i >= 0 && matches.item(i) !== this) {}
            return i > -1;            
        };
}

しかし、JavaScriptで利用したい関数の全てに対してpolyfillを書くのは現実的には不可能です。こうした理由を含めて様々な理由から、クライアントサイドでJavaScriptを書く際にはjQueryと呼ばれる、ブラウザ間の差異を吸収し、様々な使いやすい関数を追加したライブラリが今まで広く用いられてきました。

ところが、たとえjQueryを利用したとしても、実装が困難な場合があります。例えば、

リモートサーバに置いてあるテーブルデータをajaxで取得する間は、テーブルをhiddenにしておくが、ロードが終了したらvisibleにする。けれども途中でロードを止めるボタンをクリックした場合は、その代わりに「ロードを止めた」という旨を表示する

という処理をしたいとします。このとき、ロードを止めたにもかかわらず、ajaxの結果が返ってきたコールバックが発火してしまうと、テーブルが表示され、かつ「ロードを止めた」旨も表示されるということになるかもしれません。このように、本質的に複雑な条件分岐を持つような場合には、とたんにバグ無く条件分岐を全てカバーするのが困難になります。言い換えると、操作AとBがあるとき、A→Bの順で行う操作と、B→Aの順で操作が行われるとき、その操作が冪等になってほしい時には冪等になり、条件付きの場合はその条件をどこか一箇所で管理したいという希望があるでしょう。

これが難しい理由は、jQueryで実装しようとすると、本質的に「現在描画されているDOMの情報を参照して、次のDOMを書き換える」という処理を行うことになるためです。しかし、描画に必要な情報が全てDOMとしてhtml上に現れてきているかというと、実際は必ずしもそうではないはずです。また、仮にDOM構造に現れていたとしても、それらの情報が散らばっていると、それを集めるのは依然として困難です。例えば検索ツールで、様々な検索フィルタを既に選択しているかどうかで取り消しボタンが表示されるか変わるといったことを考えると、それらの全てのフィルタに対して、DOM上で選択されているかどうかの条件分岐を書くよりも、内部に「選択したかどうか」という隠れ状態があって、その隠れ状態から今のDOMが構成されていると考えた上で、その隠れ状態で条件分岐を書く方が自然です。

そこで、クライアントの状態をstateとして持ち、そのstateに応じてレンダリングされるページが決まるとしたら、どうでしょうか。これはある意味ではMVCモデルで、Modelに状態を持ち、Viewがmodelの内容に応じて描画されるというのに似ていますが、Viewの内部にも仮想的に状態を持っており、その状態に応じてページをレンダリングするという機構を考えます。このとき、stateが変更されたときに自動的に必要となる部分の再レンダリングが行われるとします。そのとき、stateに対するビューの実装と、どのコールバックによってstateが変化するかという2点のみを私たちは考えればよくなります*1

このようなことが可能になるのがReactの利点の一つで、他のJavaScriptの類似のライブラリに比べて、JavaScriptとHTMLのDOMが密に連携した書き方ができるので、データフローの見通しが良くなり、モジュール化することも容易になります。そこで、Reactをクライアント描画のライブラリとして選択することにした、とします。

RailsとReactをどう統合するか

私の知る限り、ReactとRuby on Railsを統合するための方法は、おおざっぱに3つに分けられます。

1. Railsのsprocketsで管理する

いにしえのRailsでは*2JavaScriptcssや画像と共にsprokectsと呼ばれるアセットパイプラインで管理されていました。言い換えると、Railsの管理のもとでJavaScriptのコードを書くというスタイルです*3。この場合、JavaScriptのライブラリはnpmで導入するのではなく、著名なライブラリはrubyのgemとして頒布されており、それをRailsに組み込むという形で利用していました。

しかしReactを利用しようとなると、React上で実装されているawesomeなコンポーネントを利用したくなることが多くなると思います。こうしたライブラリはRubyの世界ではなく、JavaScriptの世界、つまりnpmのパッケージとして頒布されているものがほとんどですが、こうしたときに、npmで管理されているライブラリを自由に導入するのに困難があると考えられるので、javascriptのコードはモダンなビルド環境(webpackとnpm/yarn)の上で書きたくなります。

2. RailsAPIサーバとし、ReactはクライアントWebアプリとして個別に実装する。

サーバとクライアントを独立して実装するのは、特にプロトタイピングの場面では少し難しいことがあります。それは、アジャイル開発の過程で頻繁にAPIが書き換わるので、その変更に応じてクライアントとサーバを同時に書き換えないと不整合を起こすことがあるためです。特にこれはRESTのようなルーティングが分かれてカッチリしたAPIを設計しようとすると、この問題は強くなります。設計変更によるオーバーヘッドが大きくなってしまう一方で、開発初期から理想的な設計を常にできるとは限らないので、APIを切り替えるという手間が生じると、その分だけ開発速度が下がってしまいます*4

また特に、Webアプリケーションには、車輪の再発明が必要となる場面が多くあります。例えば、ユーザのセッション管理が必要だったり、CSRFトークンの受け渡しが必要だったり、ルーティングが必要だったりします。これらは、Railsのデフォルトの機能を使えば悩まなくてすむのに、自前で実装しようとするとバグを誘発してしまったり、実装がそもそも大変だったりしてしまうでしょう。ルーティングを含めたクライアントの実装を、全てJavaScriptの世界で完結させるならばそれでも良いですが、Railsのレールから外れることによる再実装のデメリットを甘んじて受け入れたくないというときは多いと考えられます*5

3. Rails上にReactを組み込み、rubyのパッケージ管理とjsのパッケージ管理を共存させる

そこで、Railsのassets pipelineと、webpackで管理されるReactのコードを共存するという方法が、三つ目に考えられます。この方法を利用すると、webpackでJavaScriptのライブラリを自由に導入できるという利点を残しながら、Rails Wayに則って開発を進めることができます*6

github.com

Reactにおける ReactDOM.render(element, container); が、ビューの内部で呼ぶことのできる react_component(ComponentName, Props, Options)というヘルパー関数に対応します。Railsのビューをエントリーポイントとしてpropsを渡すと、以後はReactの世界でコンポーネントを描画できるという形になっています。

まとめ

もちろんここで紹介した方法がベストとは限りません。動的なページが必要かどうかというところから疑うことも時には必要でしょう。RailsとReactの共存は、サービスの規模や開発者のリソースなどによって、取るべき選択肢は変わりますので、長所と短所を検討しながら、ベストな選択をしていくことが望ましいと考えられます。

参考文献

blog.toshimaru.net

*1:もちろんstateが複雑な入れ子になり、その更新が条件分岐を持つものになるように複雑になる場合は、Reduxのようによりデータフローを意識したライブラリを導入することが必要だと考えられます

*2:今もデフォルトではそうですが

*3:javascriptというより、CoffeeScriptとよばれる、alt-JSの一種を利用することが多かったです

*4:昨今ではGraphQLとよばれる、単一のエンドポイントでリソースの問い合わせが行えるようなクエリ言語が登場し、APIの設計をある程度柔軟にできるため問題を緩和してくれる可能性はありますが、まだライブラリ等は少なく、簡単に利用可能であるかというとわかりません

*5:例えば既にstaticに作ったサイトを改修するために、Reactで再実装するのは、限られたリソースの中では大変です

*6:もちろんその欠点として、RailsとReactが密結合になってしまうことによる弊害は様々あります

MacのNotes.appで過って削除されたメモを復元する

tl;dr

Notes.appを使っているとき、sqlite3の書き込みがエラーになったようで、クラウド上への同期もとっていなかったことでNotes.appのメモがあらかた消えてしまった。サルベージを試みた記録を遺しておく。

f:id:sharply:20180319132936p:plain

What I have done

元のファイルのありかを特定しようとする

~/Library/Containers/com.apple.Notes/Data/Library/CoreData/ExternalRecords/NotesV4/{UUID}/ICNote/_records/{[0-9]+}に拡張子がnotesexternalrecordの謎のファイルが大量に残っているが、このファイルはあくまでSpotlightで検索するときに、メモに対して貼っているエイリアスのようなものであるために、そこに実体が残っているというわけではない。実際にファイルサイズは0byteであった。

sqliteの素のファイルを復元しようとする

~/Library/Group Containers/group.com.apple.notes/NoteStore.sqliteNoteStore.sqlite-shmNoteStore.sqlite-walが、また~/Library/Containers/com.apple.Notes/Data/Library/NotesNoteV4.sqliteNoteV4.sqlite-shmNoteV4.sqlite-walがあった。

NoteStore.sqliteに書き込みが失敗し、新しいファイルで上書きされてしまっているようだった。NoteStore.sqlite-walは数MBあるため、もしかしたらデータが残っているのではないかという期待があったが、stringsしてバイナリを見る限りでは、メモの中身が見えるというわけではなかった。

ダメ元でsqlite3でこのデータベースに接続してみることで、ジャーナリングをリカバーしてくれるのではないかと考えたが、sqlite3 NoteStore.sqlite vaccum;といったコマンドを叩いたところで、.sqlite-shm.sqlite-walファイルが消えるのは確認できたが、.sqliteファイルのサイズが変わらず、ジャーナリングはそのまま失われたような挙動にみえた。

このことは、もとの.sqliteファイルが消されてしまったので、Notes.appを起動したときに新しい.sqliteファイルが作られて上書きされてしまったので、リカバリが実行できなかったのではないかというのが1つと、そもそもNotes.appでは、sqlite3の書き込みにログ先行書き込みを使っているようだが(Write-Ahead Logging)、ログ先行書き込みではログを書き出していることによってアンドゥ、リドゥができるので、実際の差分を.sqlite-walファイルに書き出すことをしているわけではないと考えられる。これらのことから、.sqlite-walには実体が入っていないと思われる。

そこでsqliteファイルをリカバリして、Note.appのもとのメモを完璧な形で復元するということは諦めた。

キャッシュを探す

Spotlightで検索したとき、消えたはずのメモが検索結果に出ることから、どこかにテキスト情報としてキャッシュされているのではないかと考えた。そこで、Spotlightのキャッシュファイルのディレクトリから、メモの内容に一致するファイルを抜き出すことを考えた。

# cd /.Spotlight-V100/Store-V2/{UUID}/Cache/
# tree

ここに、文字としてキャッシュされたファイルが保存されている。 このまま普通に全文検索してしまうと、保存されているPDFなどの関係ないテキストデータが大量にひっかかってしまう。そこで、Markdownのように箇条書きで*を列挙していくと、に変換してくれるというNotes.appの挙動を利用して、文字としてを検索するということにした。

# find . -type f -print | xargs grep • | less -S

これを使って、が含まれるメモについてはある程度復元することができた。そうでないメモについては、頑張ってどんな内容が含まれていたのかを頑張って思い出して、ユニークそうな単語を検索することで発見するという操作を繰り返した。

Conclusion

iCloudなどと連携して、オンラインにバックアップを常にとっておくようにしよう。

ガベージコレクション(GC)

tl;dr

ガベージコレクションGC)についてサーベイしたので、メモを残す。

ガベージコレクションのアルゴリズムと実装

ガベージコレクションのアルゴリズムと実装

本文中の多くの記述は、上記の書籍を参考にした。筆者は素人なので、詳細な記述や実装については参考文献を参照されたい。

Introduction

プログラムを書く上では、例えば入力長が分からない入力を受け取るためなどでメモリを動的に確保したい場合がある。その際ヒープ領域とよばれる領域にメモリを確保するが、使い終わった際には解放しなければ、確保しているが使われない領域が生じるメモリリークが発生してしまう。

しかし一般に、その解放を手動で管理することは面倒である。プログラマがどの時点で確保した領域が不要となるか、その全てのオブジェクトに対して追跡することは厄介である。そのために言語処理系やライブラリでメモリ解放の手間を肩代わりするような機能が実装されている。これがガベージコレクションである。

確保したメモリ領域を手動でfreeする場合に比べて、ガベコレのある言語を用いると実装が楽になる一方、GCが起きる条件を制御できず、また多くの実装で無視できない停止時間が存在するといった問題点はあるが、小さいオブジェクトが大量に死ぬような場合ではある種のGCのほうが有利であり、どちらを選択するのが良いかは時と場合による。

GCの評価基準

スループット(単位時間当たりの処理能力)とレイテンシ(要は最大で停止している時間)はトレードオフにあり、GCの種類によりどちらにより重きを置くかは変わる。またヒープの使用効率を向上させる(例えばコピーGCで、断片化を防ぐために生存しているオブジェクトをつめるような実装がある)ことが必要となる場合もある。

C, C++では

Cでは基本的にはGCはない。mallocしたヒープ領域は自分でfreeしなければならない。 C++でもnewした領域はdeleteしなければならないが、スマートポインタというものがあり、boostに実装されているほかC++11では以下のものがある。スマートポインタは、確保したメモリ空間を、スコープから外れたときに自動で開放してくれるポインタである。

  • unique_ptr, shared_ptr, weak_ptr unique_ptrはメモリの所有権をそこに限定するもの。shared_ptrは所有権を参照カウンタによって管理し、共有することが可能である。

参照カウンタ

そのポインタを参照するごとにカウンタをインクリメントしていき、参照しなくなるとデクリメントする。カウンタが0になるとそのカウンタはどこからも参照されないということになり、安全に回収することができる。

このときの問題点として、循環的にオブジェクトを参照したいときにshared_ptrでは、その領域が解放されなくなってしまう。これを防ぐためにweak_ptrが用いられる。弱い参照と呼ばれるが、これは所有権を持たずにメモリを参照できるポインタである。例えば木構造で親子の双方向にポインタを持ちたい場合などに、これを用いることが可能である。

その他、利点として最大停止時間が短いこと、ポインタを走査するという手順が必要ないこと、逆に欠点としてカウンタの処理や実装が煩雑となることがあげられる。

またC/C++上でGCを実装したライブラリもある。

  • Boehm-GC

スマートポインタでなくともmallocのかわりの関数を呼ぶと、自動でfreeしてくれるGCは古くから提案されている。このときGCする対象となるオブジェクトを知りたいが、どのオブジェクトが参照されているかを知るためには、そのオブジェクトをさすポインタがあるかどうかで区別するしかない。しかしながら、入っている値が単なる数値なのかポインタなどのかは区別することができないので、全ての値をポインタと見なしてオブジェクトを辿り、辿れないオブジェクトをゴミとして回収する。このような仕組みで動くGCを保守的GCと呼ぶ。

従って、もしスタックに載った値がたまたまオブジェクトを指すポインタになってしまうような場合は、そのポインタの先がゴミであっても回収されず、データセットによっては大きいスペースリークが発生してしまうことが考えられる。32bitのマシンでは、このようなコリジョンが発生してしまうことは可能性としてそれほど低くないが、64bitのマシンでは場合にもよるが無視できるレベルだろう。

APGAS型の並列計算言語であるX10も、C++にトランスパイルした場合はこのGCによってヒープは管理される。

sharply.hatenablog.com

少し注意しなければならないというか、当たり前のことであるが、参照を外さないとGCで回収されない。逆に言えば参照が外れてしまうとGCで回収されてしまう可能性がある。例えば双方向連結リストでメモリ空間を節約するために、左右のポインタのxorを持っておくという方法があるが、そのような方法で管理しているポインタはこうしたGCでは辿ることができないので、オブジェクトが破棄されてしまうおそれがある。

Haskell

コピーGCをベースに、Cheneyのアルゴリズムや世代別GCを取り入れている。以下のスライドをもとに詳説を試みる。

CS240h Lecture slides

https://takenobu-hs.github.io/downloads/haskell_ghc_illustrated.pdf

Haskellでは、RTS(Runtime-System)がネイティブスレッドやGCについて司る。

コピーGC

生きているオブジェクトを別領域に全てコピーし、もとの領域を全て解放するようなGC

  • Stop the World
    • 実行系は全て停まってガベコレのスレッドだけが動く

コピーGCはゴミを捨てるのにほとんどコストがかからないために生きているオブジェクトが少ないほど有利であり、また別領域に詰め込むことでヒープのフラグメンテーションが起きないことが利点である。そして、GCの途中で参照関係にあるオブジェクトが近接するようになるので、GCに有利である。

デメリットとしては、ヒープ領域の使用効率が悪化すること、保守的GCとの相性が悪いことがある。これは、コピーGCでヒープの値のアドレスが変わってしまうと、それをさすアドレスも書き換えなければならないが、保守的GCをしたいような場合には、スタックなどに積まれている値がアドレスであるか値であるか分からないため、書き換えられるか分からないということがあるためである。

世代別GC

少しの長生きしたオブジェクトは長生きするが、残りのオブジェクトは量が多く、すぐ破棄されるという経験的な性質をもとに、アロケートされたオブジェクトは第一世代に属し、Haskellの場合では生きているオブジェクトが少ないときに有利なコピーGCで回収し、ある程度長生きしたオブジェクトは第二世代に移行し、一般にはそこでは回収しないか、低頻度でコピーGCやMark & Sweepで回収するという仕組みである。

これを組み合わせ世代別コピーGCは、JVMの実装でもみられるが、全く書き換えられないオブジェクトが何度もコピーされるのを防ぐことができる。ここでは高頻度でガベコレされる領域Nurseryと、長生きする領域Generation0, step1とGeneration1を分ける。Nurseryは高頻度にガベコレされる(Minor GC)が、一定以上長生きしたオブジェクトはstep1に移行し、Generation1へと昇格し、それほど高頻度にガベコレされなくなる(Major GC)。

ところで、JVMでもこうしたフィーチャーは実装されているが、Javaのような言語ではthisがあるせいで、Generation1に相当するオブジェクトがNurseryへのポインタを持つことがしばしば起こる。このとき、Nurseryを注意深くガベコレしなければSeg4が起こるため、Nurseryの高速なガベコレを妨げる。これに対してHaskellではmutationがrareであること、またIORefで触るような場合はどのみち遅いことから、一般の場合でそれによるオーバーヘッドはそれほど大きくならない。

また更にmajor GCにおいてはParallel GCが可能である。これはGCのスレッドが複数あり、そのそれぞれがコピーGCを行うが、複数のスレッドが同じオブジェクトをコピーしようとすることがある。しかしそうであったととしても、immutableな小さいオブジェクトであるならば複数コピーされることによるオーバーヘッドはそれほど大きくないため、ロックせずにガベコレを並列で走らせることができる。これで純粋関数であることで柔軟さを得ることが可能となる。

このように、ハイスループットGCHaskellRTSでは実現している。

Go

Go1.5〜でのGCはレイテンシの短縮にフォーカスしている。

Mark & Sweep

  • Mark
    • ポインタを辿り、オブジェクトにマークする
  • Sweep
    • マークが付けられていないオブジェクトは捨てて良い

しかしこの場合、GC実行中にプログラムが動作してポイントの様態が変化してしまうと、本来捨てるべきでない領域をSweepしてしまうかもしれない。こういった問題を防ぐために、一般にはSTWで実行系は全て停まってガベコレのスレッドだけが動くようにしている。しかし当然、STWの間は実行系が動けず、そのレイテンシはヒープ使用量が大きいほど増大することが見込まれる。

また一般に、Mark & Sweepの特性として、以下のものがある。

  • 保守的GCとの相性が良い
  • CopyGCに比べて、フラグメンテーションが起きやすい。また開放されたメモリ空間も非連続になりがち。

Go1.5〜では、Tri-color markingとよばれるオブジェクトを3分類し、コンカレントGCでほとんどSTWを起こさずにGCすることが可能となっている。これはインクリメンタルGCの一種である。

Rust

Rustでは生存時間が型レベルで管理されており、生ポインタを触ることは一般に忌避される。そこで何らかの型でラッピングしてポインタにアクセスする必要がある。

postd.cc

qiita.com

qiita.com

Rustではヒープ領域を確保する方法はBox, Rc, Arcがある。Boxはunique_ptrに対応するようなもの、Rc、Arcはshared_ptrに対応するようなものだと認識している。例えば再帰的データ構造を定義する際、Haskellではそれが参照であることを明示する必要はないが、RustではBoxで明示的にヒープを確保していることを示す必要がある。またRcとArcの違いは、それがスレッド間で共有されるかどうかという点である。

ところが、BoxやRcで指す先の値をmutableにしたいという要請がある。これを一般化するとimmutableなオブジェクトの内部に、mutableなオブジェクトを持つ要請であり、こうした要請に対してRefCell、Cellが用いられる。Cellは単純な型ぐらいに対してしか使えないので、参照型を持つ場合ではRefCellを使うことが多く、Rcと組み合わせてRc<RefCell<T>>のようになる。更に更に、スレッド間でmutableなオブジェクトを共有したい場合はMutexを用い、Arcと組み合わせてArc<Mutex<T>>のようになる。

困ること

ところで、こうした参照カウント方式ではDAGならともかく、循環リストや双方向連結リストのようなグラフ構造を持つことができない。

agtn.hatenablog.com

qnighy.hatenablog.com

これに対してTyped Arenaという方法があるが、領域を開放するためのインターフェースとして実装されているのは、全て捨てるということしかできない。つまり参照しなくなったオブジェクトがArena内にあったとしてもそれをガベコレすることができない。参照しなくなる要素が増える場合、メモリリークが大量に発生することになってしまう。

doc.rust-lang.org

これに対応する方法としては2つであり、1つはfreeListを作り、そこに削除した要素を登録し、次にnewする際にその要素を用いるようにする。ただしその場合は削除した要素が飛び飛びになっている場合、近接して確保したい領域が離れて確保される懸念がある。もう1つの方法としてはある程度使われない要素が増えた時にもう1つArena領域を取り、参照されている要素を走査し詰め込んだ後、元のArenaを全て捨てるというさながらコピーGCのような実装をするかである。

どちらにしてもTyped ArenaはArena内部に持つ要素の型と生存時間が管理されることは利点であるが、参照しなくなった領域に対するメモリリークに対する解決策ではないように思われる。

その他調べたこと

  • RubyはMRubyではマーク&スイープ、Ruby2.1からは世代別GCを取り入れている。
  • Pythonは参照カウントで管理。循環参照GCは世代別GCで管理している。
  • JavascriptのV8は世代別GCで、マイナーGCはコピーGC、メジャーGCはマーク&スイープGC

まとめ

きわめておおざっぱな分類は以下。

言語 種類 特徴 詳細
C 基本的にはなし Malloc, freeで管理
C++11 参照カウンタ unique_ptr, shared_ptr, weak
C,C++(Boehm-GC) Mark & Sweep 保守的GC,STW ライブラリを導入
X10 Mark & Sweep 保守的GC,STW C++にトランスパイルするとBoehmGCが使われる
Haskell コピーGC STW Cheneyのアルゴリズム+世代別GCなど
Go1.4 Mark & Sweep parallel STW
Go1.5〜 Mark & Sweep Concurrent/Incremental
Rust 参照カウンタ 生存時間で管理 Rc, Arc, Arena

参考

www.slideshare.net

GolangのGCを追う | SOTA

postd.cc

dic.nicovideo.jp

qiita.com

zonomasa.hatenablog.com

世代別ガベージコレクション - Wikipedia

seesaawiki.jp

http://matsu-www.is.titech.ac.jp/~endo/gc/gc.pdf

Mostly-Concurrent Mark & Sweep GC のアルゴリズム

qiita.com

dev.classmethod.jp

UEFI環境下でのgrubでLinuxとWindows 10をデュアルブートする

Windowsでのブートの制約

Dual boot with Windows - ArchWiki

上記より、Windows 10ではUEFI-GPTあるいはBIOS-MBRの組合せでしかブート出来ません。ところで旧来のBIOSブートに比べてUEFIブートは高速であることが知られています。またMBRに比べてGPTでは基本パーティション数の制限が緩和されているというように、UEFI-GPTブートがモダンなブート環境であることは間違いないでしょう。特にWindows 10をクリーンインストールするとき、デフォルトでパーティションを切ろうとするとEFI System Partition (ESP) を作る挙動がみられるように、UEFI-GPTでのブートを推奨しているようです。

ここでは、もともとLinuxを起動する際に用いていた従来のBIOS-MBR型で定義されていたgrubのブートをUEFIブートにリフトし、Windows 10とのデュアルブート環境を構築することを目論みます。grub2.02 rc2を用いました。

予めWindows 10の高速スタートアップ機能は無効にしておきましょう。

grub2の仕様

デュアルブート対象のLinux上で作業します。まずは既存のgrubから、UEFIWindowsをチェインして呼び出すことを考えます。そのためにはUEFIWindowsを認識させなければなりません。しかしos-proberを行っても、Windows Boot Managerを発見できません。そこでwikiを参照して/etc/grub.d/40_customに書き込んで、grub-mkconfigしました。

GRUB - ArchWiki

# grub-mkconfig -o /boot/grub/grub.cfg

こうしてgrubのエントリを追加すればもうWindows 10を起動できるようになったのかというとそうではなく、grubのメニューから追加したエントリを選択するとInvalid signatureと言って怒られます。これはBIOS型のgrubからUEFIブートローダーをチェインして起動することが出来ないためであると考えられます。

後で試してわかるのですが、LinuxBIOSブートで起動している場合はUEFIブートのOSを検出してくれませんが、UEFIブートで起動した場合はos-proberWindows Boot Managerを検出でき、systemd-bootやrEFIndと同様にgrub-mkconfigで自動でUEFIWindowsを探してくれます。そこで上記の手順を省略し、下記の方法でUEFIブートによって起動してからgrub-mkconfigを行えば、手動でWindowsのエントリを追加する必要はないと考えられます。

そこで次に、UEFIモードでgrubをインストールすることを考えます。UEFIの大きな特徴として、一つのESP内に複数のブートローダーを共存させることができます。 ここでは既存のMBRブートのパーティションを残し、Windows 10インストール時に生成したESPにWindows Boot Managerと共存させる形でgrubをインストールすることにしました。/dev/sda1がESPであったとします。

# mount /dev/sda1 /mnt
# grub-install --target=x86_64-efi --efi-directory=/mnt --bootloader-id=grub

としたいのですが、このコマンドを実行するとUEFI経由で起動しろと怒られます。

efibootmgr: EFI variables are not supported on this system.

この状況でUEFIブートを行ったとしても、このgrubは起動できません。ブートエントリにgrubが追記されていないためです。しかしUEFIのブートエントリに追加しようにも、内部的にはefibootmgrを用いているようですが、従来のBIOSブートで起動したlinux側からはそれを編集することができません。UEFIシェル上でbcfgコマンドでブートローダーを追記してもよいのですが、失敗するのが怖いのでgrubの機能を用いて自動でやってもらいたいと思います。そのためにはUEFI経由で起動する必要があります。

UEFIシェルを触る

先述の理由からUEFI経由で起動したいのですが、BIOSからUEFIの設定を変えてgrubから起動しようとしても、今grub-installしたブートローダーはエントリには表示されないので、UEFIシェルを叩いて直接起動するしかありません。BIOSのブートオプションでUEFI shellを選択します。このシェル画面で先ほどgrub-installしたefiファイルを実行します。

> fs1:
> cd EFI
> cd grub
> grubx64.efi

こうしてから起動すると、grubのブート画面が立ち上がります。この画面でLinuxを選択するとEFI経由で起動したことになるので、再びgrub-installすることでファームウェアのブートエントリにgrubを追記することができます。こうすると、再起動した時にUEFI経由でgrubの画面が表示され、Windows 10を選択するとgrub経由で起動できるようになります。

以上のようにして、grubによるWindows 10とLinuxのマルチブートを行いました。ただしこの場合、BIOSブートでインストールされたWindows 7などを今までのgrub経由でブートしていた場合は、UEFI経由のgrubからでは起動できなくなります。

関連URL

uefi

Unified Extensible Firmware Interface - ArchWiki

EFI システムパーティション - ArchWiki

Rustの覚書

全体的に

postd.cc

https://japaric.github.io/discovery/

インストール

rustを手元の環境に導入する方法として、rustc(rustコンパイラ)を直に入れるほかにrustupと呼ばれるツールチェーン管理用のソフトウェアを介してインストールする方法がある。これはnightlyビルドやstableビルドなどが選べたり、バージョン管理が可能だったりするので、nightlyビルドでなければ通らないライブラリを使うときに個人的には便利であった。

blog.rust-lang.org

Closs Compile

github.com

によれば、

It’s hard to find a cross C toolchain (and cross compiled C libraries) between different OSes (except perhaps from Linux to Windows). A much simpler and less error prone way is to build natively for these targets because they are tier 1 platforms. You may not have direct access to all these OSes but that’s not a problem because you can use CI services like Travis CI and AppVeyor. Check my rust-everywhere project for instructions on how to do that.

ということだそうだ。

所有権・参照

qiita.com

qiita.com

Box, Rc, Cell, Refcall

agtn.hatenablog.com

qiita.com

ヒープ領域を確保するためのBox、C++のスマートポインタに相当するようなRcなど。

Optional / Result型

qiita.com

Logger

qiita.com

コマンドライン引数

qiita.com

文字列処理

qiita.com

HaskellにおけるHTMLのテンプレートエンジンを調査する

HaskellはWebフレームワークだけでもYesod, Snap, Happstackといった重厚長大型のフレームワークに加え、軽量なもの/API向けのものでもSpock, Scotty, Servantなど乱立しているが、Template Engineとて例外ではない。Blaze-html, shakespeare, mustache, stache, ede, lucid, heistなどこれもまた様々存在し、特にSpockやScottyといったフレームワークではテンプレートエンジン選択の自由度が高いので、どれを使おうかと思い、調べたのでここにメモを残す。

Template Engine

stackoverflow.com

stackoverflowでも何を使えばよいか、議論となっている。

stache

stache: Mustache templates for Haskell

Mustacheという汎用テンプレートエンジンのhaskell実装の1つ。 ただMustache自体の仕様として記法が簡潔でよいのだが、if文の分岐がBooleanのTrue/Falseしかできないなど、少し凝ったことをしづらい面がある。 ただAesonのインスタンスをそのまま渡せるのは心強く、Viewに送りたいデータを新しいデータ型として定義し、deriveJSONを呼んでおけば、あとはテンプレートにそのインスタンスを渡すだけでよいというのは手軽である。

記法は以下のような形。

Hi, {{name}}! You have:
{{#things}}
  * {{.}}
{{/things}}

こうすると、things配列の要素が.のところに順々に出力される。

Mustache

mustache: A mustache template parser library.

これも上と同じMustacheの実装。Data.AesonのValueを渡せるのはよいが、Maybe型の値のデータをみたとき、Nothingの時の値は空白が入っていることを期待したらnullが入っているというのはstacheと異なる点。そういった点で、stacheの時と同じmustacheファイルに対して同じ挙動を示すというわけではなかった。下の記事によれば、

Mustache templates in Haskell - Tutorials

it again makes simple things complex using Aeson's Value (good) and at the same time introducing its own Value type (with conflicting names of constructors and naturally not so numerous instances).

とのことで、Mustacheを使うぐらいであればstacheを使ったほうがよさそう。

ede

ede: Templating language with similar syntax and features to Liquid or Jinja2.

Mustache風味のHTMLベースのテンプレートエンジンだが、こちらのほうが表現力が高い。

qiita.com

こちらもData.AesonのValue型をObject型に変換し、それをテンプレートに代入してrenderしてくれる機構が備わっているので、テンプレートに与えたいデータをデータ型で用意しておけばよいのは変わらない。

--Unwrap a Value to an Object safely.
fromValue :: Value -> Maybe Object

記法はこんな感じ。

<div class="checkbox">
  {% for chara in characters %}
    <label class="checkbox-inline">
      <input name="chara" value="{{chara.value.name}}" type="checkbox">
      {{chara.value.name}}
    </label>
  {% endfor %}
</div>

if文では、比較演算子if-elif-elseと複数の条件で分岐させることもできるので、View側で凝ったことをすることも容易。

lucid

lucid: Clear to write, read and edit DSL for HTML

一方でこちらはhaskellの記法でHTMLを記述するDSL

ただSpockで使う場合、テンプレートを書き換えるごとにコンパイルしなおさなければならない。ede, stacheの場合は別ファイルで呼び出すので、コンパイルをし直す必要はないが、記法が誤っている場合は実行時エラーとなる。そう考えるとCompile時にValidateしてくれるlucidや、特にshakespeareではリンクのバリデーションも施してくれるので、これらのほうが型安全であると考えられる。

HTMLテンプレート?Lucidを使ってみた

Haskellの文法そのままなので、例えばリストの要素を列挙したい場合では

div_ [class_ "container"] do $
  h1_ [] "List"
  ul_ [class_ "list-group"] $
    mapM_ (li_ []) ["a", "b"]

などと書くことで、["a"], ["b"]の中身がliタグに囲まれて列挙される。

blaze-html

blaze-html: A blazingly fast HTML combinator library for Haskell

広く使われるHTMLライブラリではあるのですが、先述のLucidはこちらの改良版と言うか、blaze-htmlの問題点を解決したライブラリである。チュートリアルより、

import Text.Blaze.Html5 as H
import Text.Blaze.Html5.Attributes as A

userInfo :: Maybe User -> Html
userInfo u = H.div ! A.id "user-info" $ case u of
    Nothing ->
        a ! href "/login" $ "Please login."
    Just user -> do
        "Logged in as "
        toHtml $ getUserName user
        ". Your points: "
        toHtml $ getPoints user

記法はこんな感じで、これの何が問題かというと、下記のブログによればいくつかあるのだが、div, id, head, mapといった要素はbaseとconflictしているのでqualifiedして呼ばなければならないし、AttributeとElementの名前がコンフリクトするものがあるので、そうするとAやらHやらを頭に付けなければならない。結果としてコードが見にくくなってしまうのだ。それよりかは、Lucidのようにすべてsuffixに_をつけるほうが統一性があり、見栄えも良さそうだ。

Lucid: templating DSL for HTML

shakespeare

shakespeare: A toolkit for making compile-time interpolated templates

yesodで使われているテンプレートエンジン。hamletはこれにdeprecateされた。拡張子としては.hamletがHTML、.juliusがjs, .luciusCSSに対応している。 記法はこんな感じ。

<div .container>
  <h1> All Posts

  <div .jumbotron>
    <ul>
      $forall Entity id post <- allPosts
        <h4>
          <li>
            <a href=@{PostDetailsR id}>#{blogPostTitle post}

タグは閉じないで、インデントで親子関係を示す。HTMLに近い記法ができる一方でコンパイル時に変数が確実に埋め込まれる。たとえば@{}では型付きのURLが埋め込まれるため、それが有効なURLかどうかコンパイル時にバリデートされる。これがリンク切れを起こさない秘訣だ。変数の代入は#{}を使う。このとき、hamletではXSS attackを防ぐために適切にエスケープしてくれる。

制御構文は$から始まる行に記述し、if, elseif, forall, caseなど場合分けや、Maybe型への対応なども充実している。hamletはQuasiquotesでコード内部に埋め込むこともできるし、外部ファイルに記述することもできる。外部ファイルの場合はTemplateHaskellで参照され、コンパイル時に組み込まれる。

上記はyesodの場合で、これはscreencastから持ってきた例だが、yesodと無関係に使うこともできる。

www.yesodweb.com

sites.google.com

heist

heist: An Haskell template system supporting both HTML5 and XML.

snapというフレームワークで採用されているテンプレートエンジン。これは実際には試していないので、ウェブ上の例を見てみよう。

<bind tag="special">special-id</bind>
<div id="${special}">very special</div>

<bind tag="message">some text</bind>
<p><message/></p>

snapframework.com

heistの特徴となる主なタグはbindapplyで、bindはその名の通り変数を束縛し、 applyは<apply template="nav"/>のようにすると部分テンプレートを代入することができるらしい。Haskellからどう呼び出すかというと、公式サイトの例をみると

factSplice :: Splice Snap
factSplice = do
    input <- getParamNode
    let text = T.unpack $ X.nodeText input
        n = read text :: Int
    return [X.TextNode $ T.pack $ show $ product [1..n]]

というコードに対して、bindSplice "fact" factSplice templateStateとすると、<fact>タグにfactSpliceがbindされるので、<fact>5</fact>120になるとのことだ。