anti scroll

ブラウザと小説の新しい関係を模索する

reactive programming / getterは共変、setterは反変

reactive programmingについての動画(語り手がハイテンション)を見つけました。

http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote-Duality

まだ前半しかみてないのですが、途中getterとsetterを「共変(covarience)」と「反変(contravarience)」という概念を使って説明している箇所があって、あんまりピンと来なかったので、少し落ち着いて考えてみました。

というわけで以下は備忘録です。

サブタイプ

動画の人は「コーラはソーダのサブタイプ」って説明してました。

ソーダ型はコーラ型を含むので、ソーダ型で記述できた式は、ソーダの部分をコーラに置き換えても動作するはずです。

で、こういう状況を

ソーダ型 >= コーラ型

なんて書くことにします。

共変と反変

例えば型をもとに新しい型を作る型構築ルールRがあったとします。

で、これをソーダ型に適用したR(ソーダ型)と、コーラ型に適用したR(コーラ型)が、

R(ソーダ型)>= R(コーラ型)

という風に、元の関係を維持しているとき(つまりR(ソーダ型)の部分を、R(コーラ型)で置き換えることができるような状況になっているとき)、Rを「共変」と言います。

逆にR(コーラ型)の部分が、R(ソーダ型)で置き換え可能な状況になっている場合は「反変」と言います。

で、動画(の前半)で言ってることは、元のサブタイピングの関係に対して、getterの型構築子は共変だけど、setterの型構築子は反変になるから気をつけてね、ってことだと思います。

getterは共変

例えば、NumのサブタイプをIntとします。

で、Numは他にもFloatとかもサブタイプに含むものとします。

Num >= Int
Num >= Float

続いて、getterを作る型構築ルールを次のように定義します。

getter(A) = () -> A

つまり、getter(A)は型Aから「何かしたらAが返る」という関数型を生み出すものとします。これをNum/Intに当てはめると

  • getter(Num):() -> Num
  • getter(Int): () -> Int

となります。

さて、プログラムの中に、getter(Num)が求められている処理があったとして、それをgetter(Int)に置き換えることはできるでしょうか。

これは問題ありません。

なぜならgetter(Num)を処理できる式は、getter(Num)の返り値となるNum型を処理できるのであり、そこがInt型を返すgetter(Int)に置き換わったとしても、問題なく処理できるはずだからです。

つまり getter(Num)な部分は、getter(Int)で置き換え可能なので、

getter(Num) >= getter(Int)

となります。

これは元のサブタイピングの関係(Num >= Int)と方向が一緒なので「それぞれの型を返す関数を作る」getter型構築子は共変(covarient)である、と言えます。

setterは反変

次にsetter型をつくる型構築ルールを次のように定義します。

setter(A) = A -> ()

型Aを受け取って何かをする、という型です。

つまり

  • setter(Num): Num -> ()
  • setter(Int): Int -> ()

このとき、setter(Num)が求められている箇所を、setter(Int)に置き換えることは出来るでしょうか。

これは出来ません。

なぜなら、Int型だけじゃなくFloat型も受け取るであろうsetter(Num)を前提に書かれた箇所を、Int型しか扱えないsetter(Int)で置き換えることはできないからです。

しかし、その逆はできるのです。

つまり、setter(Int)を受け取る部分を、setter(Num)に置き換えることは出来ます。

なぜなら、setter(Int)が受け取る型のすべて(といってもIntだけですが)を、setter(Num)は受理できるからです。

よって、型変換後の両者の対応関係は、

setter(Int) >= setter(Num)

となり、これは元のサブタイピングの関係性(Num >= Int)と方向が逆なので、「それぞれの型を受け取って何かする関数を作る」というsetter型構築子は反変(contravarient)である、と言えます。

(こんな感じの理解でいいのかな?)

参考:反変性と共変性(計算機科学)

nehan.jsの正規表現セレクタ

nehan.jsのversion5から、セレクターのエレメント名の部分が正規表現で記述できるようになっています。

サンプル

例えば、ヘッダー要素なら全てに適用したいスタイルがある時は、こんな風に書けます。

// すべてのh1〜h6
Nehan.setStyle("/h[1-6]/", {
   "font-weight":"bold"
});

// .header以下のh1〜h6
Nehan.setStyle(".header /h[1-6]/", {
   "color":"#FF0000"
});

h1h6のそれぞれにスタイルを書くよりは楽ですよね。

htmlタグにはヘッダータグのように、名前の中に意味を内包するタグもあるので、こうやって名前を正規表現でグルーピングできるのは便利だと思います。

特に自分専用のカスタムタグを作るときなんかは、重宝するかもしれません。

注意

正規表現で指定できるのは、エレメント名の部分だけです。

次のように、クラス名に該当する部分を正規表現にすることは出来ません。

// こういう指定は出来ない。
Nehan.setStyle("div/¥.(?:b|strong)/", {
  "font-weight":"bold"
});

// こういう指定なら出来る。
Nehan.setStyle("/(?:b|strong)/", {
  "font-weight":"bold"
});

nehan.jsで閉じタグの不要なタグ(single tag)を定義する

前回発表したアイコンの埋め込み特殊タグですが、こういうタグをnehan.jsでどうやって定義するのかを紹介したいと思います。

おさらいですが、先月に発表したfaタグはこんな風に宣言します。

ハートのアイコンは、<fa name="heart">

imgタグとかhrタグみたいに、閉じタグがありませんよね。

実はこういう閉じタグが不要なタグを定義する場合、事前にnehan.jsのパーサーに対し、その旨を教える必要があるのです。

じゃないと、通常のタグと同様に「閉じタグがあるもの」としてパースしてしまい、結果としてfaタグに続く全てのコンテンツがfaタグの「中身」として登録されてしまいます。

なので、こんな風に指定して、事前にfaタグが閉じタグのないタグであることをnehan.jsに教えます。

Nehan.addSingleTagByName("fa");

後は、通常のスタイル設定と一緒で、このタグの振る舞いを定義します。

Nehan.setStyle("fa", {
  display:"inline",
  width:"1em",
  height:"1em",
  onload:function(ctx){
    var markup = ctx.getMarkup();
    var icon_name = markup.getAttr("name");
    markup.setContent("<i class='fa fa-" + icon_name + "'></i>"); 
    markup.setAttr("pasted", true);
  }
});

やっていることは、

  • onloadでマークアップの読み込みをフック
  • markupの中身を通常のfont-awesomeの書式(iタグを使った書式)に書き換える
  • markupの中身をnehan.jsに組版させず、そのまま貼付けさせるために、pasted属性をセットする

というものです(縦書き文庫では、実はもう少し色々とやっていますが、ここでは簡単の為に最低限の処理だけ書きました)。

最後のpasted属性に付いては補足が必要です。

実はこの属性、まだ公式のドキュメントに書いていないのです。

なぜかというと、運用の仕方とか命名について「いいのかな」ってモヤモヤしているからです。

もしかしたら、そのうち仕様とか命名とか変わるかもしれません。ご注意ください。

Font Awesomeのアイコンが簡単に埋め込めるようになりました

Font Awesomeのアイコンを簡単に埋め込めるタグfaを用意しました。

name属性にアイコン名を指定します。閉じタグは不要です。

例えばこんな感じで書くと…

ユーザー:<fa name="user">
スター:<fa name="star">
ハート:<fa name="heart">
ハート(赤):<fa name="heart" style="color:red">
ローディング:<fa name="spin spinner">
回転する歯車:<fa name="spin cog">

こんな感じに表示されます。

f:id:convertical:20140523104932p:plain

詳しくはマークアップヘルプのアイコンフォントを入れるを参照してください。

HTMLの閉じタグを省略する記法について、パフォーマンスの検証と考察

nehan readerで長文エントリーを読むときに、少し前まで正常にパースできていたページが、ある日から突然レイアウト崩れを起こしてしまったりすることがあります。

で、調べてみたら、閉じタグの省略を使ったマークアップに変更されたことが原因のようでした。

この閉じタグの省略ルール、ルールが割と複雑で、パーサーが作りにくいので好きではないのですが、書き手にとっては読みやすいし、字数が減るので帯域は削減されるのはもちろん、ブラウザの表示パフォーマンスも(それなりには)速くなるはずです(理由は後述)。

検証コード

ただ、どのぐらい高速になるのかが少し気になったので検証してみました。

閉じタグを省略しないケースの検証コードはこんな感じで…

$(function(){
  var parts = [], node = "<li>hoge</li>";
  for(var i = 0; i < 10000; i++){
    parts.push(node);
  }
  var text = parts.join("\n");
  console.time("no abbr");
  document.getElementById("dst").innerHTML = text;
});

省略した場合はこういう感じのコードで検証しました。

$(function(){
  var parts = [], node = "<li>hoge12345";
  for(var i = 0; i < 10000; i++){
    parts.push(node);
  }
  var text = parts.join("\n");
  console.time("with abbr");
  document.getElementById("dst").innerHTML = text;
});

省略したバージョンでは、入力データのサイズを同じにする為、12345という文字をリストの文字列に追加しています。

そして、いずれもbody.onloadのタイミングでタイマーを止めています。

 <!-- 省略なし -->
<body onload="console.timeEnd('no abbr')">

<!-- 省略あり -->
<body onload="console.timeEnd('with abbr')">

検証結果

結果はこんな感じです。

time(msec)
閉じタグなし 224
閉じタグあり 2159

閉じタグを省略したケースが、10倍ほど高速という結果に。

速度差が出る理由の考察

閉じタグがある場合は、再帰的な入れ子を考慮して、対応する正しい閉じタグの位置を検索するロジックが入るから遅くなるのではないかと。

例えば、

<div>aaa<div>bbb</div>ccc</div>

では、aaaの位置から閉じタグを探すと、最初にbbbの右にある/divが見つかりますが、そこに至る前に入れ子divが開かれているので、スキップしてcccの位置から再び/divを探さなければなりません。

一方で閉じタグの省略と見なせるパターンでは、こういう再検索は不要で、もうそこがタグの終了位置だと確定して次のパースに進むことが出来るので速いのだと思います。

トレードオフ

グーグル検索のトップページみたいに、とんでもないアクセスがあるけど、ページの内容そのものにはドキュメント的な意味が少ない場合なら、帯域を削減するマークアップは意味がありそうです。

けれども、ページにきちんとした内容(文章)がある場合は、マークアップのルールが厳密な方がパーサーは作りやすいので、クライアントがパースしやすいマークアップで配信した方が、ドキュメントの利用価値は上がるような気がします。

nehan.jsで始めるfunctional stylesheet入門

いつも思うんですけど、

  • CSSセレクタのパスを書いてスタイルを設定する
  • 同じものをjavascriptで取得してイベントを設定する

って二度手間じゃないですか?

どっちもDOMノードを指定して、何かの値を設定するってことですよね。

設定するものがスタイルかイベントかってだけの違いです。

なのに、CSSの作法とjsの作法をそれぞれ別々に覚えて設定するっていうのは、設定がそれぞれ別のファイルに散らばってしまってわかりにくいし、なによりマークアップの変化に弱そうです。

なので一緒に書けたら楽だなあ、などと僕は思うのですが、nehan.jsではまさにそういうことができます。

スタイルとイベントを同時に設定してみる

例えばこんな風に設定できます。

Nehan.setStyles({
  ".nehan-fade-in":{
    "color":"green",
    "oncreate":function(dom){
      $(dom).fadeTo("slow", 1.0);            
    }
  }
});

ポイントとなるのは、oncreateなるコールバックです。

.nehan-fade-inで指定されているノードが実際に作成されるタイミングをoncreateでフックし、フェードインの処理を追加しています。

ちなみにクラス名やId名を「nehan-」で始めるというのはnehan.jsの仕様です。 nehan.jsの外の世界で組版されるものとスタイルを衝突させないための仕様です。

関数でスタイルを分岐

次に、functional stylesheetらしく、マークアップ名で色を分岐してみましょうか。

Nehan.setStyles({
  ".nehan-fade-in":{
    "color":function(ctx){
      var markup = ctx.getMarkup();
      switch(markup.getName()){
      case "h1": case "h2" case "h3": return "red";
      case "h4": case "h5" case "h6": return "blue";
      default: return "green";
      }
    },
    "oncreate":function(dom){
      $(dom).fadeTo("slow", 1.0);            
    }
  }
});

cssプロパティのcolorに指定されている値が関数になっていることに注意してください。

関数を指定した場合は、returnで返したい値を出力します。

独自の文法を作ってみよう!

nehan.jsにはoncreateの他にonloadというコールバックも用意されています。

onloadは「そのセレクタに該当するスタイルを全て読み終わり、これから組版計算を開始する直前」をフックするコールバックです。

言葉にすると複雑なようですが、ようするに具体的な組版計算が開始される前に、マークアップの内容やらCSSの値を書き換えてしまうことで、別の組版結果を導くためのものです。

何の役に立つかというと、例えば独自のマークアップ文法を定義するときに便利です。

TIPというタグを作ってみる

ここでは、クリックするとマークアップで囲まれた内容がポップアップするタグtipを考えてみます。

例えばこんなマークアップをしたら、

<tip title='nehan.js'>名前の由来は「ネイティブ組版」</tip>

その結果として

  • 画面にはtipタグの中身ではなく、title属性に設定された文字列を表示させたい
  • その文字列をクリックしたら、中身の説明文がポップアップするようにしたい

とします。

これを実現するには、

  • tipタグの組版が始まる直前をフック
  • コンテンツの内容をdatasetに退避
  • タイトル属性の内容をコンテンツに上書き
  • oncreateにてtipタグがつくるDOM要素を受け取り、onclickで退避させておいた元コンテンツをポップアップさせる処理を書く

みたいにすれば良さそうです。

実際に実装してみましょう。

Nehan.setStyles({
  "tip":{
    "display":"inline",
    // このタグの組版を開始する直前をフック
    "onload":function(ctx){
      var markup = ctx.getMarkup();
      var title = markup.getAttr("title", "no title"); // title属性を取得。ない時は"no title"が返る
      var content = markup.getContent();
      markup.setContent(title); // 中身をタイトル属性の値に置き換える
      markup.setData("content", content); // contntをdata属性(dataset)に保存しておく
    },
    "oncreate":function(dom){
      $(dom).click(function(){
        alert($(this).data("content")); // onclickで、保存しておいたdata属性をalert
      });
    }    
  }
});

上記は最低限の処理しか書いていませんが、例えば「title属性が入っていない時は無効だから何もしない」みたいな処理を加えても良いと思います。

こうやって好きなように自分のローカルな組版文法を定義し、その振る舞いをプログラマブルに制御できる、というのは通常のウェブ開発では体験できないことだと思いますし、なにより楽しいです。

結論

やはりスタイルも関数で書けると、制作者の意図が伝わりやすい気がします。

これらのスタイル指定を一つのファイルにまとめておけば、自分の作った特殊なタグ機能を他者にプラグインとして公開することも容易です。

面白い機能が作れた人は、是非教えてください。

OCaml用の麻雀ライブラリ

フォルダ整理していたら、二年ほど前に書いたOCaml用の麻雀ライブラリが発掘されました。

しまっておくのも無駄だし、バックアップも兼ねてgithubに上げておくことに…

https://github.com/tategakibunko/ocaml-mjlib

一応、ゲームやプレイヤーなども含めて抽象化されている(っぽいけど忘れた)。

ちゃんとユニットテストも付いてるから、それなりに動くんだと思います。

縦書き文庫では、ろくにテストもせずに上げ、苦情ドリブンで対応する僕ですが(個人的にソーシャルデバッグと呼んでいる)、 こういうロジック系のライブラリでは流石にユニットテストぐらいは書きます。

ちょい見る限り、どこか抜けはあるかもしれませんが、日本ルールの役は一通り得点計算できてる(はず)。

ちなみに、よくブログ記事で麻雀の処理を書いてみた、みたいなので、アマタ、シュンツ、コーツをパースして「はい、おしまい」みたいな人をよく見かけますけど、それって麻雀全体の得点計算ロジックの1割にも満たない部分ですからね。

というのも、麻雀って、手元にある14牌の状態だけで判断できる役とか得点なんて殆どないのです…

得点を正しく計算しようと思ったら、とにかく色々なコンテキストを検証する必要があるので。

まずそもそも手元の14牌だって、最後の一枚がツモなのかロンなのかってので区別する必要がありますよね。

ツモかつ喰いがなければ「ツモ」なる役が付くわけですし。

さらにプレイヤー同士の席順で判断する必要のある役とか点とかルールもあるから(リーチ一発とか、ダブロンとか、チーはカミチャだけとか)プレイヤーを抽象化する必要もあるし。

面子だって、鳴かれたものなのかどうかで府の計算も変わるし、役の扱いも変わってくるし(じゃなきゃ、ただのトイトイがスーアンコになってしまう)。

また上がる前の「待ち」がどうだったかによって付く役と付かない役とか符もある。

リャンメン待ちじゃなけりゃピンフにならないし。

リャンメンじゃないならないで、そういう場合は待ちには符がついて得点が変わるし。

また場風で決まる役もあるから(風牌とかダブトン、ダブナンとか)、ゲームの進行状況も抽象化しないと正しい得点計算が出来ない。

他にも裏ドラとかリンシャンとかハイテイとかチョンボとか…まあ色々です。

とにかくゲームの状況(コンテキスト)で決まるものが多すぎるので、必然的に色々なものを抱え込んで抽象する必要があるわけです。

例えばカレンダーを扱うライブラリだってそうですよね。

単に暦とか言ったって、見る観点が色々とあるので。

時間として扱うのか、日付として扱うのか、それともある日付を基準として数えた現在までの秒数として扱うのか。表示フォーマットだって、国ごとに色々あります。

麻雀ライブラリもそういう感じです。

ちなみに麻雀ゲームは過去に色々な言語で作ったことがあるのですが(個人的な趣味です)、 一番楽に書けたのはOCamlだったと思います。

過去の組み合わせは、サーバー/クライアントの組み合わせで

とかです。

まあ経験値が上がったぶん、最新のものが楽になってるのは当然かもしれないですけど。

これ以外には、Haskellの自習用で作った簡易麻雀ライブラリ(hml)もありますが、HaskellOCamlと同じぐらい書きやすかったかもです(特にShowって型クラスが便利だった)。

ちなみにこのライブラリ、最近になって妙にforkされているのですけど、Haskellの手習いとして作ったものなので純粋にヘタクソだし、そもそも大きな山の一割ぐらいしか上っていないので「いいんかな?」という感じです。

外部の埋め込み作品に独自のスタイルを設定する方法

外部の埋め込み作品に、独自のスタイルを設定する方法を用意しました。

具体的には、作者が自前で用意したスタイルファイルを外部から読み込ませる形になります。

通常の埋め込みコード

まず通常の埋め込みコードですが(先頭部分だけ抜粋すると)こんな感じになっていると思います。

var TbThumb = {
  nid:2980,
  width:640,
  //width:"80%",
  height:480,
  fontSize:20
};

大きさの設定ぐらいは出来るけれども、それ以外の細かい設定はできませんでした。

そこで、こんな風にしてスタイル用のスクリプトを指定できるようにしてみました。

スタイルスクリプトを指定した埋め込みコード

var TbThumb = {
  theme:"http://yoursite/path/to/theme.js",
  nid:2980,
  width:640,
  //width:"80%",
  height:480,
  fontSize:20
};

新しく追加されているのは、

theme:"http://yoursite/path/to/theme.js",

の部分です。

"http://yoursite/path/to/theme.js" の部分はお使いのサーバーのURLに置き換えてください。

theme.jsの中身は例えばこんな風に書きます。

Nehan.setStyles({
  "body":{
    "font-size":"16px",
    "padding":{
        "start":"10px",
        "end":"10px"
    },
    "border-color":"wheat",
    "border-style":"solid",
    "border-width":"5px",
    "border-radius":"5px",
    "background-color":"wheat"
  }
});

このスタイルで埋め込んでみた結果

背景を変えて角を丸くしただけですが、再生すると普段とは違う表示になります。

注意事項

スタイルを設定する際にとりあえず覚えておいてほしい点を二つ上げておくと、

  • CSSプロパティはfont-sizeのようにハイフンを使った書き方になる点
  • margin/border/paddingなどを指定する方向として論理方向(start/end/before/after)を使っている点

です。

CSSのプロパティ名については、例えばfont-sizeのことをfontSizeのようには書けない、ということです。

論理方向については、日本語の縦書き(tb-rl)ならstart/endが上と下、before/afterが右と左、とでも覚えておいてください。

より具体的なスタイルスクリプトの書き方

興味のある人はhttp://tategakibunko.github.io/nehan.js/を参照してください。