ザネリは列車を見送った

ブログという名の備忘録

CoffeeScript によるデザインパターン(Composite)

Rubyによるデザインパターン』をCoffeeScriptで書く試み。
目次

単独のタスクと、それらが集まった複合タスクを同一に扱う

タスクにはそれのみで所要時間を持つ最小単位のもの(単独タスク)と、
いくつかのタスクをまとめたもの(複合タスク)があるとする。
まず、単独タスクをふたつ、それを自身にサブタスクとして登録できる複合タスクをひとつ作る。

第一引数に畳み込みに使用する関数を、題二引数に初期値を取る。
名前を返す関数getName, 所要時間を返す関数getTimeRequired は
全てのタスクの親クラスであるTaskに定義し、
子クラスのコンストラクタでそれぞれの名前、所要時間を設定するようにした。
複合タスクの所要時間は自身のサブタスクの所要時間を合算したものとしており、
畳み込み関数「@subTasks.reduce ((sum, time) -> sum + time.getTimeRequired()), 0)」を使用して
計算している。

var task = new MakeBatterTask();
console.log(task.getTimeRequired()); // 「4」
var subTask = new AddDryIngredientsTask();
task.addSubTask(subTask);
console.log(task.getTimeRequired()); // 「5」
task.removeSubTask(subTask);
console.log(task.getTimeRequired()); // 「4」
複合タスクを表すクラスを定義する

composite1.coffee では単独タスクも複合タスクもTaskを継承し、
個別の複合タスクでサブタスクを扱うメソッドを用意していたが、
複合タスクごとにいちいち個別に定義していては辛いので複合タスクを表すクラスを定義し、
個別の複合タスク(今回の例では MakeBatterTask)がそれを継承する形に修正する。

MakeBatterTask(どうでもいいけどバターではなく Batter。こねもの、みたいな意味らしい。
今回は小麦粉と砂糖をこねたものを指すようだ。
しっくりくる訳が見当たらなかったので"ケーキの生地"くらいに捉えておこう)自体は、
その複合タスクを構成するサブタスクの登録のみでシンプルになった。
サブタスクの追加、削除は親クラスである CompositeTask でまかなうようにしている。
(実行例は composite1.coffee と同一のため割愛)

タスクを JavaScript の配列として扱う

原著では Ruby で配列要素を操作する演算子をオーバーロードすることにより
配列のように扱う例を示しているが、
JavaScript の場合配列操作に演算子ではなく push, pop, shift, unshift などのメソッドを使用しており、
また演算子オーバーロードも容易ではないため(beyond.js というライブラリを使えば実現可能らしい)
今回は割愛する。
CompositeTask が Array を継承したパターンはこちら。

Arrayクラスのメソッドをそのまま使用して、サブタスクの追加・削除(取り出し)・参照などができる。

var task = new MakeBatterTask();
console.log(task.getTimeRequired()); // 「4」
var subTask = new AddDryIngredientsTask();
task.push(subTask);
console.log(task.getTimeRequired()); // 「5」
task.pop();
console.log(task.getTimeRequired()); // 「4」
console.log(task[0].constructor.name); // 「AddDryIngredientsTask」
サブタスクに自身の親タスクを持たせる

これは原書のRubyとそんなに代わり映えしないが、
サブタスクの追加時にサブタスクの親タスクとして自身を設定し、
サブタスクの削除時にサブタスクの親タスクを null に設定するバージョン。

サブタスクから、自身の親を参照できている。

var task = new MakeBatterTask();
console.log(task.getTimeRequired()); // 「4」
var subTask = new AddDryIngredientsTask();
task.addSubTask(subTask);
console.log(task.getTimeRequired()); // 「5」
console.log(subTask.parent === task); // 「true」
task.removeSubTask(subTask);
console.log(task.getTimeRequired()); // 「4」
console.log(subTask.parent); // 「null」
specs2で単体テスト

最後に、いつも通りテストを書…こうとしたらテストケースで配列の reduce が実行できなかったため、
単体テストでのみ読み込む JavaScript で実装することにした。
Array.prototype.map の自前実装を参考に。

25行目以降が今回追加分。
で、テストケースはこのように。

CoffeeScript によるデザインパターン(Observer)

Rubyによるデザインパターン』をCoffeeScriptで書く試み。
目次

給料の変更を通知する

最初に、従業員を表す Employee クラスに
給料の変更を通知したいクラスのオブジェクトを直接渡すバージョン。

var e = new Employee("zaneli", "プログラマ", 100, new Payroll());
e.setSalary(200); // 「zaneliのために小切手を切ります! 」「彼の給料はいま200です! 」
e.setSalary(300); // 「zaneliのために小切手を切ります! 」「彼の給料はいま300です! 」
console.log(e.getSalary()); // 「300」

setSalary するたびに、経理部門にその変更が通知される。
変更を通知したい対象が増えた場合、この形では柔軟に対応できないのでさぁどうしよう、
ということで、次に Employee クラスにオブザーバを追加できるメソッドを用意し、
そこに変更を通知したいクラスを追加していくバージョン。

ここでは、update メソッドを持ったクラスのみ受け入れるように
addObserver で存在演算子「?」を使用してチェックしている。

var e = new Employee("zaneli", "プログラマ", 100);
var p = new Payroll();
var t = new TaxMan();
e.addObserver(p, t, null, 1, "hoge");
e.setSalary(200); // 「zaneliのために小切手を切ります! 」「彼の給料はいま200です! 」「zaneliに新しい税金の請求書を送ります!」
e.deleteObserver(t);
e.setSalary(300); // 「zaneliのために小切手を切ります! 」「彼の給料はいま300です! 」
console.log(e.getSalary()); // 「300」

上記のように update メソッドを持ったクラス以外(null, 1, "hoge")を渡すと、

undefined has no method 'update'
Number has no method 'update'
String has no method 'update' 

とワーニングメッセージを出力するようにした。
また、「e.observers = [null, 1, "hoge"]」とオブザーバを直接上書きすることもできてしまうため、
notifyObservers でもupdate メソッドを持っている場合のみ実行するようにした。

Observerを関数オブジェクトとして扱う

Strategy パターンのときと同様の流れだが、
オブザーバである Payroll と TaxMan をクラスではなく関数オブジェクトとして表してみる。

var e = new Employee("zaneli", "プログラマ", 100);
e.addObserver(payroll, taxMan, null, 1, "hoge");
e.setSalary(200); // 「zaneliのために小切手を切ります! 」「彼の給料はいま200です! 」「zaneliに新しい税金の請求書を送ります!」
e.deleteObserver(taxMan);
e.setSalary(300); // 「zaneliのために小切手を切ります! 」「彼の給料はいま300です! 」
console.log(e.getSalary()); // 「300」

addObserver でのチェック方法、notifyObservers での実行方法が少し変わっている。
notifyObservers 時に「observer?.call?(@)」と Employee を this として呼ぶのではなく、
addObserver 時に「@observers.push observer.bind(@)」と
Employee を bind しても意味的には同じだと思っていたが、
こうすると deleteObserver できなくなる(observer.bind(@) と observer は別物なので)。

オブザーバに対する責務を分離する(継承)

Employee クラスは本来従業員を表すためのもので、
notifyObservers, addObserver, deleteObserver を直接持っているのはあまりよろしくない。
オブザーバを取り扱う Subject クラスを用意し、それを Employee クラスが継承する形にしてみる。

これ以降、実行例は observer3.coffee と同じため割愛。

オブザーバに対する責務を分離する(関数オブジェクト単位のミックスイン)

次に、ミックスインにより Subject の機能を Emplayee にオブザーバに対する責務を持たせてみる。
CoffeeScript(JavaScript)でのミックスインの実現はいくつか方法があるようだ。
まずは、The Little Book on CoffeeScript に紹介されているミックスインを参考に、
関数をそのままミックスする手法を試してみる。

うーん…これはこれで面白いやり方だけど、
ひとまとまりの機能であるはずの notifyObserversFunc, addObserverFunc, deleteObserverFunc を
それぞれ独立して定義しているのがイマイチ。
やはり Subject クラスのように、ひとまとまりの機能を表すクラスをミックスしたい。
The Little Book on CoffeeScript には「クラスの拡張」としてこれの実現方法を紹介しているが、
ミックスインの実現のために Module クラスを継承しなくてはいけないのがどうも本末転倒な気がする。
例えば本当に継承関係にしたい Person クラスなどがあった場合、Employee は Person を継承しつつ、
Subject もミックスインするようにしたい。
The Little Book on CoffeeScript での方法では、
Module を継承したうえで、Person も Subject も同列でミックスインする、
という形で書くことになりそうだ。

オブザーバに対する責務を分離する(クラス単位のミックスイン)

CoffeeScript Cookbook にも Mixins for classes の紹介があるが、
こちらのほうが前述の希望を満たせそうだ。
「class Employee extends mixOf Person, Subject」とすれば、
mixOf の第一引数が本来継承したいクラス、
カンマ以降の第二引数以降がミックスインするクラスとして表すことができる。
さっそくこの形で実装を、と思ったのだが、
CoffeeScript サイトの TRY COFFEESCRIPT では問題なく JavaScript に変換できたものの、
Play framework ではパースエラーになってしまった。
どうも「class Employee extends (mixOf Person, Subject)」と
extends 以下をカッコで囲まないと上手く解釈できないようだ。
個人的には、カッコの有無では結構見栄えに影響すると思うのだが仕方ない。

specs2で単体テスト

テストケースでは、例によってこれを利用するのだが、
今回、単体テストでのみ読み込む JavaScript をいくつか手直しする必要があった。

まず、 console.log だけでなく console.warn で警告メッセージも出すようにしたので、
これも定義することにした。
(debug, info, error なども一緒に定義しておいたほうが良かったかもしれないが、今回は手抜き…)
また、これは specs2 で使用している selenium の都合か、配列の indexOf が実行できず
deleteObserver でエラーになってしまったので、
IE8 を Array:indexOf に対応させる」を参考に回避コードを入れた。
(IE8 に対応させるためには for-unittest.coffee ではなく
プロダクトコードでも同様の対応が必要そうだが、今回は手抜き…)
で、テストケースはこちら。

  • payroll を追加
  • taxMan を追加
  • payroll, taxMan を追加
  • payroll, taxMan を追加し、taxMan を削除
  • 不正なオブザーバ(null、文字列、数値) を追加
  • payroll を追加し、taxMan を削除(追加していないオブザーバを削除するケース)
  • 不正なオブザーバ(null、文字列、数値) を直接上書き

のテストを実行している。

CoffeeScript によるデザインパターン(Strategy)

Rubyによるデザインパターン』をCoffeeScriptで書く試み。
目次

HTML/プレーンテキストでレポート出力再び

まずは原書通りに、Strategyクラス2つ(HTMLFormatter, PlainTextFormatter)を作成し、
Contextクラス(Report)に設定/差し替えができるようにしたものを書く。
ContextからStrategyメソッドの引数に必要な値(title, text)を渡すバージョン。

Strategyの引数にContext自身を渡し、必要な値をStrategyメソッド内で取得するバージョン。

こんな感じでformatterを差し替えて出力できる。

var x = new Report(new PlainTextFormatter())
x.outputReport() // プレーンテキスト形式のレポートをコンソールログ出力
x.formatter = new HTMLFormatter()
x.outputReport() // HTML形式のレポートをコンソールログ出力
Strategyを関数オブジェクトとして扱う

ReportクラスにStrategyクラスではなく、関数オブジェクトを渡すバージョンを書いてみる。

this の formatter の引数に this を渡している…。あれ、こんなことする必要はないのでは?
ということで、Strategy関数から引数を除去し、
関数内の this から必要な title, text を取得するようにしたバージョンがこちら。

実行例はこんな感じ。

var x = new Report(outputHTML)
x.outputReport() // HTML形式のレポートをコンソールログ出力
x.formatter = outputPlainText
x.outputReport() // プレーンテキスト形式のレポートをコンソールログ出力

ところで、formatter には何でも渡せてしまうので、
例えば文字列や数値など関数オブジェクト以外を渡すと
ReportオブジェクトのoutputReportを呼んだ時点でエラーになってしまう。

x.formatter = "hoge"
x.outputReport() // 「TypeError: Property 'formatter' of object #<Report> is not a function」が発生

これを何とかケアできないものか。

Strategyの妥当性チェック(蛇足)

試行錯誤した結論として、ここからの書き換えは労力に見合う効果は無いのでは?
と思うようになったが、せっかく色々やってみたので一応残しておく。
以下がoutputReportを呼び出したときに、
関数オブジェクトでなければワーニングを出すようにしたバージョン。

この変更の目的が「不正な値を設定したときにでも実行時エラーにならないようにする」
であれば概ね目的を満たせているようには思う。

var x = new Report(outputHTML)
x.outputReport() // HTML形式のレポートをコンソールログ出力
x.formatter = "hoge"
x.outputReport() // 「String is not a function」をワーニング出力
x.formatter = 1
x.outputReport() // 「Number is not a function」をワーニング出力
x.formatter = outputPlainText
x.outputReport() // プレーンテキスト形式のレポートをコンソールログ出力

さらに、そもそもoutputReport実行時にチェックするのではなくて、
formatter設定時に検知できたほうが親切では、と思い手を加えたバージョンがこちら。

一気にゴチャゴチャしたような感じがする。
setFormatter時にチェックするのがメインの目的で、
それならコンストラクタでもチェックしないと、とコンストラクタ関数でsetFormatterを呼ぶようにし、
getFormatter関数自体を上書きされてしまえばsetFormatterのチェックが行われずにoutputReportが呼ばれてしまう、と思い
結局outputReportでもチェックするようにした。
複数箇所から同一のチェックがしたいので、safeExecuteという関数にまとめた。
第一引数が関数オブジェクトであれば第二引数の関数を実行する。
コンストラクタで関数オブジェクトを渡さない場合、ワーニングメッセージを出してもいいのだけれど、
その状態で outputReport を呼んでもワーニングメッセージ「undefined is not a function」が出続けるオブジェクトができてしまい、
それならいっそエラーを投げてあげたほうが親切では、と思いこの形になった。
ただ、よくよく考えてみるとこの修正の出発点が「不正な値を設定したときにでも実行時エラーにならないようにする」だったはずなので、
本末転倒では、という考えに至った。
Report オブジェクトを this として扱わせるために「->」ではなく「=>」を使用したり、
セッターで渡された関数オブジェクトに bind(this)したりと、
色々工夫してそれなりに勉強にはなったと思うが、
ここは妥当性チェックを行わない strategy.coffee を採用することにしよう。
(CoffeeScript Cookbook にも Strategy パターンの例があるが、これもすごくシンプルに見える。)

specs2で単体テスト

最後にテストケース。
formatterを設定し直して出力形式を変えるテストも書いた。

CoffeeScript によるデザインパターン(Template Method)

Rubyによるデザインパターン』をCoffeeScriptで書く試み。
目次

HTMLでレポート出力

まずは、月次報告をHTML出力するレポートジェネレータを作成する。
原書では Ruby の puts メソッドを使用しているので、
JavaScript では実用性は低いかもしれないけど console.log で出力することにする。

Reportクラスを作り、コンストラクタで title と text を設定している。
「@title」はJavaScriptの「this.title」となり、Reportインスタンスのプロパティとして扱える。
同じファイル内で「$ -> new Report().outputReport()」として呼んでいるので
ページロード時にコンソール出力されるが、この箇所も含めて即時関数となるので、

(function() {
  var Report;
  Report = (function() {
    ...[略]...
  })();
  $(function() {
    return new Report().outputReport();
  });
}).call(this);

Reportクラスを別のcoffeeファイルやjsファイルから呼ぶことはできない。

フォーマットによって出力内容を分岐

原書に倣って愚直に分岐させてみる。

CoffeeScriptの場合、switch文は「switch ... when ... else ...」となる。
「class @Report」としているため、今度はグローバルに定義されたReportクラスを外部から呼ぶことができる。
(とはいえ、せっかくCoffeeScriptがスコープを限定してくれているので、多様しないほうがいいと思う)
ページを表示し、コンソールで
「new Report().outputReport("html")」
「new Report().outputReport("plain")」
を実行することで挙動を確認できる。

親クラスで共通処理を、子クラスでフォーマットごとの処理を

Template Method を適用するとこのようになる。

Reportクラスは外部から参照する必要はないため、「@」を外している。
子クラスでそれぞれ実装している outputLine メソッドは、親クラスで定義する必要はない。
もちろん、子クラスにも outputLine が存在しないと、outputReport() を呼んだ時点でエラーとなる。

TypeError: Object #<InvalidReport> has no method 'outputLine'
specs2で単体テスト

こちらの仕掛けを使用して、textarea に出力されたレポートの内容をアサートする。

CoffeeScript によるデザインパターン(目次)

これは『Rubyによるデザインパターン』を CoffeeScript で書いてみようというシリーズです。

Rubyによるデザインパターン

Rubyによるデザインパターン

主に自分自身が CoffeeScript の文法を学ぶ目的で行います。
Play framework 上で開発し、こちらの方法を使用して specs2 でテストしています。
また、jquery-1.9.0 との併用を前提としています。

目次
参考にしたブログ

Pythonによるデザインパターン(目次) - None is None is None

MessagePack for Java を Scala から実行する

MessagePackJava版をScalaから使ってみた。

なんでこんなことをしたかというと

MessagePack for Scalaは現状 Scala 2.10には対応していないらしく
(java.lang.NoClassDefFoundError: scala/reflect/ClassManifestエラーが発生する)、
2.10 対応版フォークを使用する、
自分で公式リポジトリから clone してワークアラウンドコードを入れてみる、
など対応方法はあると思うけど、
MessagePack for Javaを使ってみるとどんな感じになるかを試してみた。
今回は JSON ⇔ MessagePack の変換。

MessagePack to JSON

org.msgpack.type.Value.toString() が JSON フォーマットの文字列を返してくれるので
それを呼んで scala.util.parsing.json.JSON.parseFull() とかしてやればおしまい。
ただそれでは面白くないので愚直に書くとどうなるかやってみた。
こういうのを作って、

val value: org.msgpack.`type`.Value = ... // new MessagePack().read() とかで取得
val map = Value2JsonMap.createMap(value)

こんな感じで Map[String, Any] を作れるので、後は如何様にも。
Java だと if (value.isRawValue) value.asRawValue.getString とかやるところが
パターンマッチでまかなえて良い感じかも。

JSON to MessagePack

さっきの逆パターンの object を作る。

val map: Map[String, _] = ...
val value = JsonMap2Value.createValue(map)

こんな感じで org.msgpack.type.Value を作れるので、後は write するだけ。
(型のパターンは網羅が足りてないけど…)

JSON to MessagePack(別バージョン)

上記はMapに入っているもの全部を Value にするけど、
キーを指定して特定の値のみ選択したい場合。

こういうトレイトを作っておいて、これをミックスインして使う。
たとえばこんなJSONを処理したい場合、

{
    "id" : 1,
    "name" : "zaneli",
    "friend" : [{"id" : 2}, {"id" : 3}, {"id" : 4}],
    "address" : {
      "zipcode" : "xxx-yyyy",
      "city" : "ihatov"
    }
}

こうする。

case class UserFactory(map: Map[String, _]) extends KeyValueFactory {
  def createValue: Value = {
    mapValue(map,
      intKeyValue("id"),
      rawKeyValue("name"),
      (arrayKeyValue("friend") { value: Map[String, Any] => mapValue(value, intKeyValue("id")) }),
      mapKeyValue("address")(
        rawKeyValue("zipcode"),
        rawKeyValue("city")))
  }
}

…おや、元々のJSONとcreateValueメソッドの見た目が似ていて、けっこう個人的には見栄え良く感じた。
こうなるとarrayKeyValueのところももっと簡潔にならないかなぁ…。

Facebook C# SDK を F# から実行する

Facebook C# SDK を使用して F# からFacebook API を叩いてみた。
最初にコードを示しておくと、こんな感じになった。

…うーん、ダウンキャストしたりボクシングしたりミュータブルな Dictionary を使ったりと、
あんまり F# らしいイケてるコードに見えない…。
これは僕がもっと良い書き方を知らないからなのだろうか…。

SDK の入手

NuGetを使用する。
Visual Studio拡張機能としてインストールするのが一般的(?)なようだけど、
普段 Visual Studio を使っていないのでスタンドアローン版
(っていうのかな、コマンドラインで実行できるexeファイル)を使用した。

nuget install Facebook

を実行すると、まず NuGet 自体のアップデートが行われた。

NuGet bootstrapper 1.0.0.0
Found NuGet.exe version 2.5.0.
Downloading...
Update complete.

アップデート完了後、再度同コマンドを実行する。

Installing 'Facebook 6.4.0'.
Successfully installed 'Facebook 6.4.0'.
ライブラリのロード

NuGetで取得した Facebook.dll と ZaneliFacebook.fs を F# interactive にロードする。
(ついでにモジュールのインポートも)

#r "Facebook.dll";;
#load "AccessToken.fs";;

open Zaneli.Facebook;;
アプリ認証のためのcodeを取得
let url = getLoginUrl;;

で取得したURLにブラウザアクセスして、code を取得する。
認証確認ダイアログ
code はアプリ認証後、redirectUri に指定した URI に Get パラメータとして付いてくる。
パラメータに指定している appId は Facebook アプリ設定画面で確認できる「アプリID」。
redirectUri はアプリ設定画面で設定したURLと一致、もしくは App Domains で指定したドメインのサブドメインにする必要がある。
パラメータをボクシングしているのは、FacebookClient の GetLoginUrl に
IDictionary<string, string> ではなく IDictionary<string, obj> を渡す必要があるため。
(これはボクシングではなくてもっと良いやり方があるように思う…)

アクセストークンを取得

先ほど取得したcodeを引数に使用する。

let token = getAccessToken <取得したcode>;;

この token を使用して各 API を実行する。

自分のプロフィールを取得
let profile = getProfile <取得したtoken>;;

独自で定義した Profile レコードを返すようにした。
パラメータは、dict[("access_token", box token)] としたかったが、以下のエラーが発生した。

System.NotSupportedException: 種類 'System.NotSupportedException' の例外がスローされました。
   場所 Microsoft.FSharp.Core.ExtraTopLevelOperators.CreateDictionary@45.Remove(TKey key) 場所 f:\dd\fsharp\devdiv\src\fsharp\FSharp.Core\fslib-extra-pervasives.fs:行 74
   場所 Facebook.FacebookClient.PrepareRequest(HttpMethod httpMethod, String path, Object parameters, Type resultType, Stream& input, Boolean& containsEtag, IList`1& batchEtags)
   場所 Facebook.FacebookClient.Api(HttpMethod httpMethod, String path, Object parameters, Type resultType)
   場所 Facebook.FacebookClient.Get(String path, Object parameters, Type resultType)
   場所 Facebook.FacebookClient.Get(String path, Object parameters)

どうも内部で要素を除去しているらしく、イミュータブルな IDictionary では駄目なようだ。
これも Dictionary<string, obj> に要素をセットするのではなく、もう少し F# 的にスマートなやり方がある気がする。

投稿
let result = post <取得したtoken> <投稿内容>;;

レスポンスには投稿した ID が返ってくるようだ。
パラメータとしては

  • picture
  • link
  • message
  • name
  • caption
  • description
  • category

が指定できるらしいが、今回は単純にメッセージのみ指定するようにした。
投稿結果
こちらも dict 関数でパラメータを作って渡すと以下のエラーが発生した。

System.NotSupportedException: 種類 'System.NotSupportedException' の例外がスローされました。
   場所 Microsoft.FSharp.Core.ExtraTopLevelOperators.CreateDictionary@45.set_Item(TKey key, TValue value) 場所 f:\dd\fsharp\devdiv\src\fsharp\FSharp.Core\fslib-extra-pervasives.fs:行 48
   場所 Facebook.FacebookClient.PrepareRequest(HttpMethod httpMethod, String path, Object parameters, Type resultType, Stream& input, Boolean& containsEtag, IList`1& batchEtags)
   場所 Facebook.FacebookClient.Api(HttpMethod httpMethod, String path, Object parameters, Type resultType)
   場所 Facebook.FacebookClient.Post(String path, Object parameters)

こちらは内部で要素の追加をしているようだ。

 

色々改善点は残っているように思うが、とりあえず動くのを確認できたのでめでたし、としておこう。