MathJaxをAutoPagerizeと両立させた話

モチベイション

日記blogMathJaxに対応させたのですが、AutoPagerizeで継ぎ足したページの数式が勝手には処理されてくれないので、それに対応する必要がありました。検索してもなかなか見つけられなかったので、ここに書いておきます。

コード

if(window.addEventListener) {
    var NodeInsert = function(evt) { window.MathJax.Hub.Queue(["Typeset", MathJax.Hub, evt.target]); };
    window.addEventListener('AutoPagerize_DOMNodeInserted', NodeInsert, false);
    window.addEventListener('AutoPatchWork.DOMNodeInserted', NodeInsert, false);
}

備考

  • 以前はてなスターを設置したときに書いたものとNodeInsertの内容を除いて同一です。
  • コードから明らかなようにAutoPatchWorkにも対応しています。

会社の勉強会でSwiftの発表をした話

会社のiOSチーム勉強会で発表の順番が回ってきたのでSwiftの良さについて発表をしました。会社の人にすすめられたので、せっかくだし資料を公開します。が、公開前提に作っていないし準備も不足だったので、資料だけ見てもあまり伝わらないと思います(すみません)。次回はもっと改善したい。

主題としては、Optionalという概念をちょっと違った角度から見ると類似物がいろいろあるよ、という話(とその準備)です。今更Swift入門なんかやっても仕方がないのでちょっと進んだ話題を取り扱おうとしたのですが、だいぶ力不足だった感があります。

補足

まったく言及していませんが、要するにモナドの話をしています(chainと呼んでいるものが>>=に対応します)。60ページで

a.chain({ $0.b }).chain({ $0.c }).chain({ $0.i })
// と
a.chain({ $0.b.chain({ $0.c.chain({ $0.i }) }) })

が等価だ、と書いて説明を省いていますが、これがモナド則の結合律に対応しています。だからといってOptional Chainingを>>=にすり替えたのは、いささか強引でよくなかったかもしれませんが……

BFTaskというのはBolts Frameworkに含まれている非同期処理のライブラリで、JavaScriptで言うところのPromiseになっています。PromiseというだけならSwiftネイティヴの実装もいろいろあるのですが、この勉強会の前の回がたまたまこれを題材としていたので(87ページのがその時のスライド表紙でした)、それを使いました。ObjCのライブラリだとid型がAnyObjectになってしまってSwiftのIntStringがそのまま使えないので、やむなくNSNumberNSStringにしています。


Optional<T>.mapの話

前置き

Swiftのライブラリ定義を眺めていると、こんなものに出くわした。

enum Optional<T> : Reflectable, NilLiteralConvertible {
    // (略)

    /// Haskell's fmap, which was mis-named
    func map<U>(f: (T) -> U) -> U?

    // (略)
}

これの話をする(ちなみに上の引用はSwift1.0のもので、1.1ではコメントが変わっている)。

Optional Chaining

よく知られているように、Swiftでは型Tと型T?とは別のものなので、T型のメソッドをT?型でそのまま呼ぶことはできない。たとえば:

class SomeClass {
    func someFunction(a: Int) -> String { /* ... */ }
}

let neko: SomeClass? // = ...
let mimi = neko.someFunction(0)  // これは書けない。nekoはSomeClass?型だから。
let mimi = neko!.someFunction(0) // これは書けるが、もしnekoがnilだったら死ぬ。

こういうときは、Optional Chainingを使えばこのように書ける。

let mimi = neko?.someFunction(0) // これは書けて、型はString?型となる。

これはつまり、SomeClass型のInt -> String型のメソッドを、SomeClass?型のInt -> String?型のメソッドへと「読み替えた」と思うことができる。

// これは仮想的な記法であって本当はこんな定義はできないが
class SomeClass? {
    // こういうメソッドに「読み替えた」と思える
    someFunction(a: Int) -> String? { /* ... */ }
}

別の読み替え

そういう読み替えができるなら、こういう読み替えもほしくなるのが人情だろう。

func f(x: T) -> U { /* ... */ }   // これを
func f(x: T?) -> U? { /* ... */ } // これに読み替える

mapはまさにそのような読み替えを行う。

func someFunction(a: Int) -> String { /* ... */ }

let a: Int? // = ...
let b = someFunction(a)     // これは書けない。
let b = a.map(someFunction) // これは書けて、bはString?型になる。

mapという名前

なぜこれがmapという名前なのか。ここで配列型にもmapというメソッドがあることを思い出すだろう。

extension Array {
    // (略)

    /// Return an `Array` containing the results of calling
    /// `transform(x)` on each element `x` of `self`
    func map<U>(transform: (T) -> U) -> [U]

    // (略)
}

T?Optional<T>の、[T]Array<T>の略記だったことを思い起こせば、この二つの定義はOptionalかArrayかの違いを除いて同じもののように見える。

実際、同じなのだ。Arrayのmapは、配列の中の値を写像して新しい配列に入れるものだし、Optionalのmapも、Optionalの中の値を写像して新しいOptionalに入れる。ArrayのmapT -> Uの関数を[T] -> [U]の関数に読み替え、OptionalのmapT? -> U?の関数に読み替えるのである。

ところで

Arrayのmapがそうであったように、Optionalのmapにもインスタンス・メソッドではないバージョンもある。

/// Return an `Array` containing the results of mapping `transform`
/// over `source`.
func map<C : CollectionType, T>(source: C, transform: (C.Generator.Element) -> T) -> [T]

/// Haskell's fmap for Optionals.
func map<T, U>(x: T?, f: (T) -> U) -> U?

ArrayではなくCollectionTypeに対して定義されているので多少対応が見づらいが、同じものである。

Haskell's fmapとの関連

ところで冒頭の引用には、“Haskell's fmap, which was mis-named”なるコメントがつけられていた(今はないのだが)。これの話をする。

このfmapというのは、Functor型クラスのfmapのことである。型クラスというのは、気分的にはSwiftのプロトコルのようなもので、型に対して制約を課す。Functor型クラスの定義はこう:

class Functor f where
  fmap :: (a -> b) -> f a -> f b

これは前節で触れた、インスタンスメソッドではないほうのmapの定義に近い。引数の順番は違うし、他にもいろいろと違う点はあるのだが、だいたい「ある型がFunctorであるとは、fmapという関数を持つことである」と読むことができる。

ではFunctorとは何か。これは元は(Haskellにありがちなように)圏論の用語で、日本語では関手と呼ばれる。関手とは何かという話をすると、これは知らない言葉が倍々ゲームで増えることになるのでここでは立ち入らない。HaskellにおいてFunctorがなにを宣言しているかというと、(とてもおおらかに言えば)「この型は性質のよい箱を作る」ということだ。ここで「性質のよい」と呼んでいるのは、「中身の型がわかっていて、任意の写像で移した箱を作れる」という意味である。たとえばHaskellのリストは、次の定義でFunctorになっている。

instance Functor [] where
    fmap = map

要するにリストはリストとしてのmapfmapとしてFunctorである、という話である。このあたりが“mis-named”なのだろう。

(ただし、ここで要請されるのは型が一致するというだけの話であるから、箱に入れてfmapで写像したものと、普通に写像してから箱に入れたものとが一致するとは限らない。写像の合成が一致する(fmap (f . g) == fmap f . fmap g)べき(should)とされているが、文法上の制約ではない。)

まとめ

そしてこの意味で、Array<T>Optional<T>もFunctorなのである。どちらも型TからTを入れる箱の型を作る操作であって、箱の中身を移して新しい箱を作るmapという関数を持つ。Optionalと配列とを同一視するFunctorという視点が、このmapには表現されているのである。

こういう話は楽しい。

参考文献


Associated valueのあるenumは自動でEquatableにならないという話

事象と原因

以下のコードはコンパイルに失敗する。

enum Enum {
    case Value
    case WithNumber(Int)
}

let b = Enum.Value == Enum.Value // エラー: "Cannot invoke '==' with an argument list of type '(Enum, Enum)'"

お前はなにを言っているんだ、と思ったが、どうやらこれは正しい挙動のようだ。

公式Blogによれば、

Simple enums that have no associated data (like MyBool) are automatically made Equatable by the compiler, so no additional code is required.

のようである。なるほど、case WithNumber(Int)の行をコメントアウトするとコンパイルに成功する。

対応

当然、自分でEnumをEquatableにして、==

func ==(lhs: Enum, rhs: Enum) -> Bool {
    switch (lhs, rhs) {
    case (.Value, .Value): return true
    case (.WithNumber(let left), .WithNumber(let right)): return left == right
    default: return false
    }
}

と定義すればよい。switchをネストしなくてよいのはタプルとパターンマッチの強みであろう。where left == right: return trueとすることもできる。

とはいえ、このケースはAssociated valueがIntで自明にEquatableなのだから、察して大目にみてほしい、とは思う。

関連: Hashableについて

The Swift Programming Languageによれば、Associated valueつきenumはHashableでもなくなるようだ。

Enumeration member values without associated values (as described in Enumerations) are also hashable by default.

Associated valueつきenumは普通のenumとは似て非なるものと考えたほうがよいのかもしれない。

参考文献


if文にBool?をそのまま投げると非直感的な評価をされる話(昔話)

注意

この挙動はβ5で変更されました。 具体的にはif optionalFalseが禁止され、nil判定はif optionalFalse != nilと明示的に書くようになりました。これによって以下のような曖昧さはなくなります。

以下は昔話として残しておきます。

昔話

if文の条件式にOptional型を渡すと(当然ながら)nilか否かで判定が行われる。

したがって以下は当然の帰結である。

let optionalFalse: Bool? = false

if optionalFalse { println("\(optionalFalse) Evaluated as true") }
// prints: "Optional(false) Evaluated as true"

ちゃんとunwrapしなくてはいけない。

if optionalFalse! { println("\(optionalFalse) Evaluated as true") }
// prints nothing

ところでこれは:

if !optionalFalse! { println("?") }
// prints "?"

!(optionalFalse!) = !(false) = trueと評価されるらしい。(!optionalFalse)!は評価できない。


UIButtonとUITapGestureRecognizerの相互作用がiOS5以前と6以降とで違うという話

モチベイション

たとえば、画面上にテキストフィールドを配置して入力を促すことを考える。このとき、画面のテキストフィールド外をタップした場合はフォーカスを外してキーボードを隠すなどの処理を行いたいことがある。こういった場合、画面全体のviewに対してUITapGestureRecognizerを追加してタップイベントを拾うのが常套手段だろう。

ところでここにボタンが登場する。仮に送信ボタンとする。ボタンはボタンで同時にタップイベントを処理しており、タップされれば反応する。このとき先に追加したUITapGestureRecognizerとの絡みで問題が起こりはしないかと心配になるけれども、特に問題はない。送信ボタンは何事もなく反応し送信する。

……iOS6以降ならば。

事象と原因

iOS5で同じことをすると、ボタンのタップアクションは無視される(ハイライトは起こる)。Stack Overflowによれば、

On iOS 5, a UITapGestureRecognizer on a button's superview interferes with the action of the button. On iOS 6, they fixed this: they introduced a UIView event gestureRecognizerShouldBegin:, and a button automatically returns NO for a tap gesture recognizer attached to a superview.

ということらしい。ちなみに、この状態でも長押ししてから離すとタップアクションが起こる(これはどうやらUIResponderのtouchesEnded:withEvent:を経由して起こっているようだが詳細は不明)。

処方箋

UIGestureRecognizerのデリゲートメソッドでタップイベントを選択的に無視することができる。こんな具合である:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    // ボタンを避ける(iOS6以降では勝手に避けてくれるけれど)
    if (touch.view == _submitButton) {
        return NO;
    }
    return YES;
}

UITapGestureRecognizerのdelegateselfに設定することを忘れないように注意が必要である。

参考


Apache2.4の後ろでsocket.ioを動かした話

あらすじ

node.jsになんとなく手を出してみて、WebSocketなるものの存在を知り、socket.ioなるパッケージで手軽に扱えることがわかったので、Apacheのバージョンを2.4に上げて(これは省略する。いずれ書くかもしれないが)設定をいろいろ試してなんとかした話。

サーバ・サイド(node.js側)

素人なのでnpmでサクッとexpress-generatorを入れてテストプログラムを生成した。

var io = require('socket.io').listen(server);
io.sockets.on('connection', function(socket) {
    console.log("connection");
    socket.on('message', function(data) {
        console.log('message');
        io.sockets.emit('message', {value: data.value});
    });
    socket.on('disconnect', function() {
        console.log('disconnect');
    });
});

クライアント・サイド

<script src="socket.io/socket.io.js"></script>
<script type="text/javascript">
    var socket = io.connect('http://192.168.0.43', {resource: 'nodejs/socket.io'});
    socket.on('connect', function(msg) {
        console.log("connect");
        document.getElementById("connectId").innerHTML = "ID: " + socket.socket.transport.sessid;
        document.getElementById("type").innerHTML = "type: " + socket.socket.transport.name;
    });
    socket.on('message', function(msg) {
        document.getElementById("receivedMessage").innerHTML = msg.value;
    });
    function sendMessage() {
        var msg = document.getElementById("message").value;
        socket.emit('message', {value: msg});
    }
    function disconnect() {
        var msg = socket.socket.transport.sessid + " has disconnected.";
        socket.emit('message', {value:msg});
        socket.disconnect();
    }
</script>

上のjavascriptはbin/wwwに、下はviews/index.ejsに書いた(それぞれ抜粋)。参考にしたのはこちら: WebSocket 事始め by Node.js + Socket.IO - Qiita:

HTML側の第3行、io.connect()の第2引数については後述する。

サーバ・サイド(Apache側)

いつまでも3000番で待っても仕方がない。しかし80番にはApacheが既にいて仕事をしている。そこでApacheから3000番へと飛ばしてくれないかというのがリバース・プロキシという仕組みのようだが筆者は詳しくない。以下はhttpd.confの抜粋である。

ProxyRequests Off
ProxyPassMatch ^/nodejs/socket\.io/(.*)/websocket/(.*)$ ws://localhost:3000/socket.io/$1/websocket/$2
ProxyPass /nodejs http://localhost:3000 timeout=600 keepalive=On
ProxyPassReverse /nodejs ws://localhost:3000
ProxyPassReverse /nodejs http://localhost:3000

1行目はフォワード・プロキシの無効化設定で、あまり関係がない。3行目と5行目がHTTPリクエストのリバース・プロキシ設定で、ここでは/nodejsに来たリクエストを3000番ポートへ寄越してくれないかと要請している。より詳しくは、3行目が/nodejs→3000番で、5行目が3000番→/nodejs(すなわちサーバからのリダイレクションの書き替え)を表している。

2行目と4行目はWebSocketリクエストのリバース・プロキシを要請している。といっても4行目は5行目と同じことをしているだけなので単純である(4行目と5行目は1対多ではなく多対1の対応を表していることに注意)。問題は2行目であるが、ここも正規表現によって見た目が複雑なだけで、第2引数を見ればさほど複雑ではない。しかし、ここを上手くやらないとWebSocket通信には失敗する。

これはsocket.ioがWebSocket通信を開始する手順による。まず上記HTMLの1行目にてnodejs/socket.io/socket.io.jsがリクエストされ、これはリバース・プロキシ3行目によって3000番ポートのsocket.io/socket.io.jsへと送られる。次に、返ってきたjavascriptによって定義された変数ioに対するconnect()呼び出しによって、nodejs/socket.io/1/へとHTTPのリクエストが送られる。この応答によって、最後にnodejs/socket.io/1/websocket/connection_id へのWebSocketリクエストが発生する。

従って、リバース・プロキシのルールは以下の条件をみたす必要がある:

  • nodejs/socket.io/1/websocket/ 以下をws://localhost:3000/socket.io/1/websocket/ 以下へ送る
  • nodejs/socket.io/1/ をhttp://localhost:3000/socket.io/1/ へ送る
  • nodejs/ をhttp://localhost:3000/ へ送る

実際には、条件の第2と第3とは統合して書くことができ(3・5行目)、第2の特殊ケースとして第1(2・4行目)を書けば上記の設定となる。

こうなりましてん

あとはテストプログラムのディレクトリへ移動してnpm startと打てばサーバが動作する。
スクリーンショット
あたかもチャットかのように見せているが実際は最後に送信されたメッセージしか表示されない。しかしサンプルとしては十分である。

補足

io.connect()の第2引数について

これはクライアントサイドのsocket.io.jsがどこを向けて通信を行うかを指定している。デフォルトでは"socket.io"となっているのだが、この場合上述のリバース・プロキシ3条件の第1と第2がそれぞれ

  • socket.io/1/websocket/ 以下をws://localhost:3000/socket.io/1/websocket/ 以下へ送る
  • socket.io/1/ をhttp://localhost:3000/socket.io/1/ へ送る

に変更される。こちらにしなかったのは、設定が多少煩雑になることと、3000番への転送をnodejs/以下に限定したかったことによる。

モジュールについて

いうまでもなく、リバース・プロキシ機能を利用するためにはhttpd.confに上記以外にも設定を行う必要がある。

LoadModule proxy_module lib64/httpd/modules/mod_proxy.so
LoadModule proxy_http_module lib64/httpd/modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module lib64/httpd/modules/mod_proxy_wstunnel.so

特に3行目はWebSocketの転送を行うのに必要である。


文章を一文ごとに分割する正規表現

モチベイション

ある文字列を一文ごとに分割することを考える。ここで文とは句点(。)で区切られた文字の一繋がりで、その末尾以外の場所に句点は出現しないとしよう(この文自体が例外であるが)。そのような文がいくつか繋がった文字列を、文ごとに分割したいとき、まず思いつくのはこのような方法である:

text.split(/。/)

この方法は部分的に上手くゆく。すなわち文字列は分割されるが、それらの文は末尾に句点を含まない。したがって目的を達するためには(多くの場合)分割された文の末尾に句点を補う必要がある。おそらくこうなるだろう:

text.split(/。/).map{|s| s + "。"}

しかし、もしここで文の条件が変わったらどうか。たとえば、文の区切りとして句点のほかに疑問符「?」が使用可能になったらどうだろう。この場合上記の手法の単純な拡張は失敗する。分割によって失われた記号がいずれだったのかを知ることができないからである。それぞれの記号ごとに分割・補完を繰り返す必要がある。

解法

正規表現の後読みを使えばこの問題は一撃で解決する。具体的には以下のようである。

text.split(/(?<=[。?])/)

ここで記法?<=は「後読み」を表す。後読みというのは、「○○に続く~」を表す記法で、たとえば(?<=A)Bという正規表現は「Aに続くB」にマッチする。「AB」ではなく「(Aに続く)B」であることに注意が必要である。これに従えば上の正規表現は「『。』または『?』に続く(何か)」にマッチすることになる。

さらにこの「(何か)」が指定されていないことにも注意が必要である。この場合この「(何か)」には「長さゼロの文字列」が対応する。「『長さゼロの文字列』にマッチする」とはこの場合文字と文字の間の隙間のことを指している。たとえば以下の式は、すべての文字間にマッチするために文字を一文字ずつに分解する。

text.split(//)

この二つを応用すれば、上の式はこのように理解されるだろう。

「『。』または『?』に続く『文字間』で文字列を分割せよ」

これこそまさに、我々の求めていたものである。