研究ブログ

2014年6月の記事一覧

検索により拡張可能なプッシュ型掲示板プログラムの裏側

このブログで何度も取りあげてきた掲示板ですが、その仕組みを簡単に紹介します。研究室の学生さんでJavaScriptプログラミニングに興味がある人がいたので書いています。
 
まず、この掲示板の作者は私ではなく、私の研究室出身の谷口さん(現在、徳島大学のポスドク)です。2度ほど大幅な書き直しがあって、現在CrossPointという名前になっています。GitHubでコードも公開されています(リンクはこちら)。 
 
大まかに動作を説明すると、ブラウザでサーバにアクセスすると、サーバから掲示板のデータが送られて、表示されます(下図参照)。

メッセージを書き込めば、そのデータがサーバに送られ、現在接続している全てのクライアントに自動的に送信されます。つまり、WebSocketを使っており、ユーザはリロードすることなく、最新の書き込みを表示できます。また、アンカーにも対応しています(上の図の">>3"など)。
 
サーバはRubyのWebSocketライブラリであるem-websocketを使っています。"em"はEventMachineの略で、非同期処理のライブラリです。掲示板に必要と思われるイベントをいくつか決めて、イベントが起こったら、クライアントにメッセージを返します。例えば、クライアントが初めて接続してきたら、画面に表示する名前と学生番号(上の画面ダンプの右上)に入力を促すため、"need-both-ids"というイベントを発行します。初めてのアクセスかどうかを判断するために、ブラウザのローカルストレージに識別子を格納しています。
 
クライアントはJavaScript+HTML5+CSSで書かれています。client.htmlを見ると、掲示板のメイン部分は
        <div id="column-container"></div>
となっており、カラムの容器があるだけです。どのようなカラムを配置するかはconfig.jsで定義します。それほど複雑ではないので、実例を見ていきましょう。
Xpt.initialize({
    app_title: "情報科学III用掲示板",
    init_columns: [
        { title: "全体のメッセージ"
        , in_filter: function(p) { return ! p.content.match(/#GROUP/ig); }
        , out_filter: function(p) { return true; }
        , in_map: function(p) { return p; }
        , out_map:    function(p) { return p; }
        , entry_placeholder: null
        , removable: false
        },
        { title:   "グループ(" + readFromStorage('group_id') + ")内メッセージ"
         , in_filter: function(p) { return !p.content.match(/#GROUP_NOTIFY/i) && p.content.match(/#GROUP/i) && p.user.group_id == readFromStorage('group_id') ; }
        , out_filter: function(p) { return true; }
        , in_map:     function(p) { return p; }
        , out_map:    function(p) { p.content += "#GROUP"; return p; }
        , entry_placeholder: "グループ内メッセージ..."
        , removable: false
        },
        { title:   "グループ向け通知"
        , in_filter:  function(p) { return p.content.match(/#GROUP_NOTIFY/i) && p.user.group_id == readFromStorage('group_id') ; }
        , out_filter: function(p) { return true; }
        , in_map:     function(p) { return p; }
        , out_map:    function(p) { p.content += "#GROUP"; return p; }
        , entry_placeholder: "グループ内メッセージ..."
        , removable: false
        },
        { title:   "TA"
        , in_filter:  function(p) { return p.content.match(/##TA/i); }
        , out_filter: function(p) { return true; }
        , in_map:     function(p) { p.content = p.content.replace(/##TA/ig, ""); return p; }
        , out_map:    function(p) { p.content += "##TA"; return p; }
        , entry_placeholder: "TAのみ書き込めます"
        , removable: false
        }
    ]
});
 
 
JSON形式で書かれており、ここには4つのカラムが定義されています。カラムごとにtitleがあり、entry_placeholderには書き込みフォームに薄く表示しておく文字列を書きます。removableがtrueなら、そのカラムはユーザが取り除けます。一番の特徴は、in_filter, out_filter, in_map, out_mapです。"in"はサーバから受けとったメッセージの処理、"out"はクライアントが書いたメッセージをサーバに送るときの処理であることを示しています。一方で、"filter"には論理値を返す関数がかけて、trueになったものだけ受信または送信します。"map"は、書き込まれたり受診したメッセージを加工する処理が書けます。
 
例で説明しましょう。"全体のメッセージ"カラムでは、in_mapもout_mapも、引数のpをそのまま返しているだけなので、特に加工はしません。しかし、サーバから受けとったメッセージを表示するときに、メッセージ(p.content)に"#GROUP"という文字列(ハッシュタグのようなもの)が含まれていると表示しないようにしています。
 
次に、"#GROUP"というハッシュタグがどのようにして付けられるか見てみましょう。これは、グループ内メッセージというカラムで生成されます。titleに、ローカルストレージに保存しておいた、group_idの値を使用しているので、「グループ(4)内メッセージ」のように見えます。このカラムは、多人数で掲示板を利用すると、書き込みが多すぎて密度の濃い議論ができないため、少人数のグループに分けるために定義しました。
 
さて、「グループ内メッセージ」に書き込むと、その"out_map"により、自動的に"#GROUP"という文字列が追加されます。また、このカラムに表示するメッセージは"#GROUP"という文字列が入っていて、かつ、このユーザのgroup_idが一致したものだけに限定されます。
 
つまり、文字列を追加したり検索するだけで新しいカラムが定義できました。もちろん、新しいイベントを定義したり、ローカルストレージに書き込む変数を増やしたりする場合には、サーバとクライアントのソース(xpt.js)に手を入れる必要があります。しかし、すでにあるデータの一部を取りだしたり、特定の人に制限して見せるという程度であれば、比較的に簡単に実現できることが分かります。
0

メンタルモデルと納得

メンタルモデルとは、まわりの世界(の一部)がどのように作用するかを理解するためのモデルで、認知心理学や教育心理学、デザインなどの分野で使われているようです。それまでバラバラだった自分の中の考えが、この言葉を知って説明しやすくなったので、これを紹介します。
 
自分にとってインパクトがあったの理由は、まず、異なる分野(本)で同じ言葉を見たからです。まず最初に、「ベストプロフェッッサー」(「よい大学の先生は何をしているか?:ベストプロフェッサーで紹介しています)で見ました。この中には、受講者のメンタルモデルを変えられるような先生たちが紹介されています。逆にいうと、普通の授業だとメンタルモデルは変わりません。例えば、「重いものは軽いものより速く落ちる」というモデルを構築している学生は、テストではきちんとした理解を示しても、メンタルモデルは従来のまま、というような例が紹介されてます。さらに、実験などでどちらも落ちる速度は同じであるという結果を見せても、「いま見た結果が特殊である」と主張して、なかなかメンタルモデルは変えないそうです。教育に関わるものとしては、メンタルモデルを変えたいですよね。
 
次に見たのは「誰のためのデザイン?」です。この中には、様々な実例をあげて、どのようにデザインしたら、利用者が適切なメンタルモデルを構築できるか、あるいは逆に、このような場合はメンタルモデルがうまく構築できないかということが説明してあります。特に、ドアの例は「あー、そうそう」と納得感が非常に強かったです。つまり、ドアを見ただけで、これが開き戸なのか引き戸なのか、どちら側に開けるのか(押すのか引くのか、など)を適切にドザインするのは難しいようです。実際、大学のドアでしょっちゅう「締切」 のほうを押してしまいます。
このドアは[引]と[押]が1枚のドアの両側に書いてあり、このドアが2枚で組になってて、片方は締切になっています。写真では[引]が書いてありますがまり、締切のほう(左側)にも[引]が書いてあり、これだけでも、締切のほうを引いてしまいそうになります。さらに、よく使うほう、つまり、締切ではないほう(写真の右側)は、よく触られるためでしょうか、[引]の字が薄くなっていているのです!こうして、はっきり見える締切のほうを引いたり押したりして「ガコッ」っとなるわけです(怒)。
 
逆に感心した実例がトイレの水を流すボタンです。日本では上か下に回すタイプが多いのですが、この場合の「上」と「下」と、「大」と「小」の対応は恣意的であり、文字を見るしかありません。ちなみに、我が家に2つトイレがありますが、それぞれ方向が異なってます!
ところが、シンガポールのあるホテルでは、円を大きさの異なる二つに分けてあって、「大」と「小」の対応が自然につくようになってます。
 
さて、上述の物体落下の例からも分かるように、一度構築されたメンタルモデルを変えることは容易ではありません。しかし、納得できそうな説明で間違えたモデルを作ってしまうこともよくありそうです。これに気付いたのはiPhoneの世界時計です。時計の背景の色は、下の写真のように2種類あります。
   
これ自体、まったく気付いてなかったのですが、気付いてからは、午前と午後に対応していると思いこんでました。しかし、海外出張中に、たまたま1時間の時差があるところで、気付いたのですが、午前6時と午後6時を境に、色が変わっています。しかし、ググってみると、午前と午後ともっともらしく説明している人もいました。
 
アインシュタインは、他の人との違いを聞かれて、「たとえば、干し草の山から針を探さなくてはならないとします。あなた方はたぶん、針が1本見つかるまで探すでしょう。私は、針が全部、見つかるまで探し続けると思います」と答えたそうです(スウェーデン式 アイデア・ブック)。いったん、納得しやすい回答が出されると、それで納得してしまいがちです。例えば、環境問題や食料自給、高齢化や少子化問題など、複雑な問題に対する説明を、データを添えられてもっともらしくされると、確かに納得してしまいがちですが、そこでも「ホントにそうなの?」と疑ってみること大事ですよね。
0