GADTというものを知った
最近OCamlをようやくversion4系列にアップデートしたのですが。
その際にGADTとかいう言葉が気になったので調べていたら、ちょうどわかりやすいエントリーが。
Detecting use-cases for GADTs in OCaml
上のエントリによると、どうやらGADTというのは「代数型に型の強制を付けるためのもの」らしいです。
どいういうことかというと、例えばとあるexprという代数型を
type expr = | Tint of int | Tbool of bool
と宣言した時、exprは中身がintの時もあれば、boolの時もある型ですよ、と宣言しているわけですが…
つまりTint 10
もTbool false
も、同じexpr型に属することになりますよ、としているわけですが…
このexpr型を使って構成される「別の代数型」というものを考えた時、単にexpr型とだけ宣言されると、表現としてイマイチになることがあるのですよね。
例えば次のようなAbstract Syntax Treeを定義したとき…
type ast = | Value of expr | IfExpr of expr * expr * expr
なんかIfExpr
のexpr * expr * expr
の部分の表現力が乏しくないですか?
exprが3つ並んでるんですけど、それぞれがどういう性質のexprを要求しているのか、よくわからないですよね。
もしこのIfExpr
を
IfExpr of (bool値のexpr) * (int値のexpr) * (int値のexpr)
みたいなニュアンスで型を制限できたら、安全だし、わかりやすくないですか?
これを、まさに可能にするのがGADTというやつらしく。
ちなみにGADTというのは、Generalized Algebraic Data Typeの略で日本語なら「汎用代数型」とでも訳すのでしょうか?
実際にexprとastをそれぞれGADTを使った型宣言(expr'とast'とする)に書き直すと、こういう感じになります。
type _ expr' = | Gint: int -> expr' | Gbool: bool -> expr' type _ ast' = | GValue: int expr' | GIfExpr: bool expr' -> int expr' -> int expr' -> int expr'
普通の代数型だとof
が来る部分に:
が来て、右側が「型の関数」みたいな記述になるのが特徴です。
で、構成される代数型に型の制約を付けたい場合はtype _ expr' = ...
みたいに宣言します。
アンダースコアの部分は「代数型に何かしらの型アノテーションがつくよ」みたいなニュアンスなんでしょうか?
こうやって作られたGADTを使うと、
let statement_by_gadt : ast' = GIfExpr (Gint 10) (Gint 20) (Gint 30)
は、IfExprの一つ目のexpr'が「bool expr'」じゃないので、コンパイルの段階でエラーになります。
一方で、GADTじゃない、普通の代数型のastを使った場合、次の
let statement_no_gadt : ast = IfExpr (Tint 10) (Tint 20) (Tint 30)
は(型の上では)エラーではありません。
IfExprを構成するメンバーは全てexpr型と規定されているだけなので、型の上では正しいからです。
これを構文エラーにするには、evalするときにundefned patternとして、型エラーを自前で書くしかないわけですが、GADTで制約していればコンパイルの段階でエラーにしてくれます。
nanocのほうがjekyllより良いかも
nanocとは
静的サイトジェネレーターとは、固定的な内容のウェブサイトを作るのに使われるサイトジェネレーターです。
ホームページ制作ツールのような複雑なIDEではなく、コマンドラインとエディタでサクっとサイトが作れるので、プログラマーには人気があります。
昨今はそうしたツールでブログを公開する人も珍しくなくなりました。
ちなみにこの記事ではnanocの使い方は説明しません。本家に丁寧な説明があるので、そちらを参照して下さい。
jekyllとの違い
Ruby製の静的サイトジェネレーターといえば、jekyllも有名ですが、最近はnanocを主に使っています。
理由は記事の変換処理を柔軟に扱えるからです。
jekyllでも記事を変換する場合に、独自の処理を設定することは出来ますが、変換エンジン「そのもの」の切り替えしかできません。
しかしnanocでは、変換処理をfilter
という単位で分割定義できて、それらを記事の「メタデータ」に応じて自由に組み合わせて適用させることができます。
例えば「コードハイライトの前処理は、プログラミング系の記事にだけ適用する」といった具合にです。
サンプルケース
マークダウンをCSSフレームワークと合わせて使った場合、デフォルトの出力では困ることがありますよね。
例えばタグに、そのCSSフレームワークで使う特別なクラス属性を付けたかったりとか。
でもマークダウンで次のように書くと、
## これはH2
もちろん次のように変換されるわけですが、
<h2>これはH2</h2>
こういう風に出したい時もあるわけじゃないですか。
<h2 class="my-header">これはH2</h2>
こういう場合、そのまま生でタグを打ってもいいのですが、せっかくマークダウンを使っているのですから、楽をしたいものです。
というわけで、nanocのfilter
を定義しちゃいましょう。
filterを定義する
要件定義として、次のように宣言したら目的のコードが出力される、としましょう。
[myheader2 これはH2]
要はこのコードをマークダウンにかける前に、正規表現で目的のコードに変換すればいいのです。
そこでlib/filters.rb
などというファイルを作り、次のような処理を書いて、my_header
という識別子のフィルタを定義します。
class MyHeader < Nanoc::Filter identifier :my_header type :text def run(content, params={}) content.gsub(/\[myheader(\d)\s+(.+?)\]/, "<h\\1 class='my-header'>\\2</h\\1>") end end
次にRules
というファイルで、今作ったmy_header
というfilterを、マークダウン変換の前に差し込みます。
compile '*' do if item[:extension] == 'md' filter :my_header # <- これを追加! filter :kramdown # マークダウン変換処理 layout 'default' end end
しかしこれだと、全てのマークダウンファイルにmy_header
フィルタを通してしまいますよね。
なので、itemにuse_my_syntax
というメタデータが設定されているときだけ適用させるようにしてみます。
compile '*' do if item[:extension] == 'md' if item[:use_my_syntax] then # 独自文法使う? filter :my_header # <- これを追加! end filter :kramdown # マークダウン変換 layout 'default' end end
比較としてjekyllの独自コンバーター定義の方法を抜粋しておきます。
class Jekyll::Converters::Markdown::MyCustomProcessor def initialize(config) require 'funky_markdown' @config = config def convert(content) ::FunkyMarkdown.new(content).convert end end
上記のconvert
という関数を拡張するわけですが、見たらわかるように引数として元記事のcontent
しかもらえないわけです。
しかしnanocでは記事に付いたメタデータを見ながら処理を分岐できるので、これは大きなアドバンテージです。
ちなみに記事のメタデータを付ける方法は、記事の先頭に次のようなヘッダーを書くだけです。
--- use_my_syntax: true ---
ヘッダーはYAML
で記述します。
最後に
最後に、nanoc.ymlのprune
について。
prune: auto_prune: true exclude: [ '.git', '.hg', '.svn', 'CVS', ".gitignore", "push.sh" ]
auto_prune
を有効にすると、使用されていないファイルを削除してくれます。
デフォルトでは有効になっていませんが、true
に設定することをおすすめします。
これを有効にしてないと、元記事のファイル名を変えた時に、古いファイルが出力先に残り続けてしまいます。
例えばあるときにtop.md
というファイルを、welcome.md
に変えたくなったとしましょう。
そのまま放置してしまうと、出力先にtop/index.html
というファイルが残ってしまいます(a.mdはa/index.htmlに出力されるのがデフォルトのルール)。
しかしprune
をtrue
にしておくとtop/index.html
は自動で削除されます。
段落ごとにコメントできる縦書き文章の作成サービスを公開しました
Brushup! について
段落ごとにコメントできる縦書き文章の作成サービスBrushup!を作りました。
パスワードを付けることもできるので、特定の人にだけ見せる使い方も可能です。
主に編集や校正の依頼に使えるツールを想定しています。
段落へのコメントを試してみたい方は、お試し用のエントリからどうぞ(コメントを投稿するにはユーザー登録が必要)。
ユーザー登録について
グーグルアカウントで登録できます。
マークアップについて
マークダウンという文法ですが、普通に文章を書く感覚で使っても、大体は思ったとおりの出力になるんじゃないかと思います。
マークダウンについては、ヘルプにも少しだけ説明しましたが、細かいことは各自で調べて下さい。
ちなみに縦書き文庫の記法は基本的にはそのまま使えるはず(全部はまだ試していませんが…)
チップ記法とか台詞記法とかは大丈夫でした。
またルビの記述ですが、Brushup!では簡易記法を用意しました。
[ruby 漢字 かんじ]にルビをふる。
みたいに書けます。多少はタイプ量が減るかと…
活動メモについて
次にプロフィールページにある「活動メモ」というものですが、自分の活動の中で報告したいものを勝手に書き込んで下さい。
サービスの性質上、プロフィールページに公開投稿を自動で一覧表示、みたいな機能は敢えて用意していません。
活動メモを使って、自分の言葉で報告したりしなかったりすればいいと思います。
ちなみに投稿した文章のURLは、連番じゃなくてハッシュ値で作られるので、自分でURLを公開しない限りはアクセスされないと思います。
もちろん非公開にすることも出来ますし、パスワードを付けて公開することも可能です。
datasetにセットしたboolean値について
ちょっとハマったのでメモ。
例えば、
<div id="foo" data-hoge="true"></div>
とかあったとき、以下の結果はどちらもfalse
$("#foo").data("hoge") == "true" // false $("#foo").data("hoge") === "true" // false
理由はdatasetにセットした"true" もしくは "false"は、boolean型として評価されるから…
typeof $("#foo").data("hoge"); // "boolean" $("#foo").data("hoge") === true; // true
ちなみにnehan.jsも、datasetの値はズボラぶっこいて、一律で文字列型のまま返しちゃっていたので、今さっき直しました。
脚本を台詞記法で書く
ちょっとした小ネタですが、縦書き文庫の台詞記法はキャラクタ登録なしでも使えます。
キャラクタ名の部分に、人物の名前をそのまま書けば、キャラクタ画像じゃなくて文字列で表示されます。
なので、ちょっとした脚本は次のような感じで書けます。
[speak 太郎 今日は天皇杯の決勝だね。] [speak 次郎 去年まで同じJ2で戦ってた同士が決勝って凄いね。]
詳しくは縦書き文庫ヘルプ | 台詞記法を参照して下さい。
チップ記法
わざわざtip
タグを書くのが煩わしいので、チップ用の記法を用意しました。
チップについては 縦書き文庫 | チップ記法 を参照のこと
文法
次のように記述します。
[tip タイトル:内容]
注意
tip
とタイトル
の間には一つ以上の半角スペースが必要です。タイトル
と内容
の間には半角のコロン:
が必要です。
台詞記法で吹き出しの有無を切り替えられるようになりました
台詞記法で、吹き出しの有無を選べるようになりました。
声に出す台詞はspeak
を使います。
[speak taro これは声に出して言う台詞!]
心のなかの台詞はthink
を使います。
[think taro これは心のなかの台詞…]
実際に表示させるとこんな風になります。
詳細は縦書き文庫ヘルプ | 台詞記法についてを参照のこと。
利用者の「規約を読みました、同意します」はウェブ最大の嘘?
おもしろそうなサービスを見つけました。
「みんな読んだとか言って嘘ついてるけど、あなたが同意した規約の中身って、実際はこんな風になってるんだよ」みたいなことが確認できるサービス。
各サービスの各規約ごとに、thumb up/downのアイコンが割り振ってあって、総合評価としてclassA〜classEでレーティングされています(classAが最も良い)。
有名どころでは、
- DuckDuckGo がclassA
- Github がclassB
- Google がclassC
- Delicious/Youtube がclassD
みたいな感じでした。
ちなみにamazonはどうかな、と思って確認してみたら、規約評価の項目は全てthumb downでしたが、classはまだ規約の調査が完了していないので付いていない(No Class Yet)、という扱いみたいです。
レビューの再利用に関する権利
amazonの規約で思い出したのですが、3年ぐらい前に「投稿したレビューの権利が、商用利用も含めてアマゾンに譲渡されることについて知ってるかい?」みたいなブログポストがありました。
Amazon and Book Review Ownership
ちょっと確認したのですが、この件に関する(と思われる)規約の文面は、今はこういう内容です。
お客様がコンテンツの投稿または素材の送信を行った場合、他の取り決めを当サイトが明示していない限り、お客様は、アマゾンに対して、そのようなコンテンツを使用、複製、変更、翻案、公開、翻訳、二次著作物の作成、配布、あらゆるメディア形態で世界中に表示できる、非独占的な、無償の、永続的な、取り消し不可能な、完全なサブライセンスを含む権利を許諾したものとみなします。お客様は、アマゾンとサブライセンスを受けた者が希望すれば、それらに対して、そのようなコンテンツに関連してお客様が送信された名前を使用する権利を許諾したものとみなされます。Amazon.co.jp 利用規約 / レビュー、コメント、コミュニケーション、その他のコンテンツ
ようするに、再利用に関する権利はamazonにあって、amazonはその権利を第三者に対してもライセンスできる、という内容です。
ずいぶんと過激な気もしますが、しかしこれはamazonの商品APIを使うようなマッシュアップ系サービスからしたら、都合が良いはずです。
いちいち全てのレビューについて、それぞれのレビュアーの許諾を得なくてはいけない、となると手続きが大変ですが、この内容なら、アマゾンだけから許諾を得れば良いことになりますし…
ただ、レビューを書いた人たちからしたら、どうか。
もし自分のレビューが「商品のプロモーション」として、なんの相談もなく再利用されたら…もやもやしますよね。
でも規約は、そういうやり方も認めるような内容に読み取れますが、実際どうなんでしょう。
縦書き文庫も「サービスを利用した際に発生した二次的な情報の権利はサイト側に帰属する」などと気楽に書いてますが、アクセスログはともかくとして、レビューはどうするべきなんだろうなあと考えると、悩ましいところではあります(それほどコメントの多いサービスではありませんが)。