ninjinkun's diary

ninjinkunの日記

失敗するプレゼンの夢

勉強会で発表する予定だが、資料が一枚もできていない。アプリのUIの話で、ボタンの角丸の部分を作る際の問題点について話す予定だった。とりあえず話は決まっているし、ライブでなんとかなるだろうと本番を向かえる。

アプリを見せながら、「ここに角丸がありますね」と言って話を続けようとする。しかし特に問題点が見つからない。何が問題だったのだろうと考えながら、冷や汗が流れ続ける。

YAPC::Asia 2014でモバイルアプリ開発について発表しました #yapcasia

もう先週のことになりますが、YAPC::Asia 2014でモバイルアプリ開発について発表しました。YAPCはエンジニアとして仕事を始めてからずっと憧れだったので、初めて登壇できてとても嬉しかったです。

自分の発表15分、gfxさん15分、二人でディスカッション10分という、ちょっとイレギュラーな構成の発表でした。その辺りの経緯を以下に書きます。

コンセプト

お客さんはサーバーサイドのエンジニアの方が多いと予想していたので、モバイル開発をするエンジニアが普段考えていることを知ってもらうことをゴールにしました。この発表の内容は、バックエンドのエンジニアが直接使える知識ではないかもしれません。しかしモバイルエンジニアと関わる仕事をしているなら、彼らがどう考えているかを知っておくことは、どこかで役に立つのではないかと思います。このため、モバイルエンジニアである自分たちが今現場で直面していて、面白いと思っている問題を共有しようというのが基本コンセプトでした。

gfxさんとのコラボレーション

自分もgfxさんモバイル開発に携わっていますが、自分はUI寄りで、gfxさんはミドル領域というかDeveloper Productivity寄りということで、やっていることは少し違います。この対比が面白いと思ったので、あまり耳慣れない Client-Fontend EngineeringCliend-Backend Engineering という言葉を使って、それぞれ違う話をしました。打ち合わせでgfxさんとお話ししていて、モバイル開発が拡大して、ミドル領域の仕事が生まれている現状がとても面白いと感じたので、それをお伝えしたくてこのトーク構成になりました。

モバイル開発の中でも、自分にとっては苦手な分野だったリリース作業やテストをGood Problemと捉えるgfxさんの姿勢を見ていると、UI大好きおじさん以外のエンジニアもモバイル開発にもっと入ってきて欲しいと強く思います。

初めはなんとなくの流れで決まったコラボレーションでしたが、gfxさんと組めて良かったです。自分としても、今後の議論の足がかりができたと思っています。

おわりに・YAPCの感想

自分が聞いていた中では、cho45のトークが面白かったです。あとはあらたまさんのモバイルAPIのトーク今月のWeb+DB Pressのzigorouさんの記事と(たぶん)繋がっていて、記事をAPI側の話、トークをアプリ側の話として聞くと倍楽しいと思います。GitHubのDelcambreさんによる、gitとの連携をどうやって進化させてきたかの話も面白かったです。

今回発表してみて、改めてYAPCの幅の広さと面白さを実感しました。自分は今はPerlを書いていない人間なのですが、それでも楽しめるし、受け入れてもらえたと思えました。HUBでだらだらビールを飲みながら、エンジニアというのは集まって技術の話をするのがどうしようもなく好きな人種であるなあ、というのを再認識した次第です。足を運んでくださった方々、運営スタッフの皆さま、そしてgfxさん、ありがとうございました。

Scala Matsuri

Scalaを作ったMartin Odersky先生が来られるというので、生Odersk先生目当てでScala Matsuriに出かけた。特にScalaを仕事で書く機会があるわけでないけれど、CourseraでScalaの講義を受講していたとき、毎回Odersky先生のビデオを見ていたので、近くで見られるなら一度見てみたいと思った次第。

Scalaの歴史を全然知らなかったので、Pizzaという言語があったことや、Scala拡張期に入れた機能を今は制限しようとしているとか、そういう話が聞けて楽しかった。急に流行ってしまうと、その後の進化のハンドリングは難しくなるだろうと感じた(当たり前だけど)。

Twitterの丹羽さんの発表の中でバルス対策の話が出たのだけど、バルス画像が出た瞬間にOdersk先生が爆笑していたのを、後ろから目撃した。

Odersk先生はバルスのことを知ってるのか…。何だか変な風に繋がってしまった気がして、不思議な気分で帰宅した。

Androidアプリの段階的リリース

Androidアプリは全体の5%のユーザーに公開するというような、段階的公開が可能です。会社でこの機能を使っているので、知見をまとめました。

目的

  • 致命的な問題 (e.g. 商品が出品、購入できない)に全ユーザーを巻き込むのを避ける
  • 不具合を減らしつつ、リリースサイクルのスピードを保つ

問題

  • 母集団が少なすぎると問題が見つからない or 報告されない場合がある
  • 変更できるのは公開するユーザーの割合のみ
  • できる限り不具合にユーザーを巻き込まないようにしながら、早めに段階を上げるという問題を考える

戦略

プラン

    • 変更点が多い場合、致命的なバグが発生する可能性がある場合
    • 5 or 10%からリリース
    • 最短で5日で全ユーザーにリリース
  • (竹は考えてなかった…)
    • 変更点が少ない場合
    • 20%からリリース
    • 最短で3日で全ユーザーにリリース

運用

  • リリースの翌日に不具合の有無を確認して、大丈夫そうなら公開の段階を一つあげる
    • サポートメール、Crashlyticsを都度確認
  • 土日のリリースは避ける
  • 不安なら段階を上げる間隔を2日くらいにする
  • これはあくまで基本プラン。リリースの優先度、緊急度によって臨機応変に対応

運用してみて

  • 毎日リリース作業があるのが地味に面倒
    • 不具合の確認作業に時間を取られる
      • 15分とかだけど、まあ仕方ない
    • 手作業なので、注意していないと作業自体を忘れる
    • このあたりは自動化の余地有りかも
  • 不具合は5~20%くらいで顕在化する印象
    • 50->100はそんなに怖くない、というかあまり意味が無さそうなので飛ばしてもいいかも
  • 不具合が出たら修正してapkを再アップロード。対象ユーザーだけにアップデートが配布される。よくできてる

他社の事例も知りたいっす。

Build Variantsで開発版Androidアプリを分ける

f:id:ninjinkun:20140818095759p:plain

Androidアプリを開発していると、開発版とリリース版のアプリを同時に入れておきたいことがあると思います。通常Appliction ID (com.ninjinkun.njkappのようなやつ) が同一だとアプリが上書きされてしまうのですが、Build Variantsを使う事で別のApplication IDを割り振ることができます。

build.gradle

productFlavors {
    staging {
        setApplicationId("com.ninjinkun.njkapp.staging")
    }
    production {
    }
}

Manifest Placeholder

この辺りは去年からできたのですが、 ContentProviderBroadcastReceiver を使っている場合、Android ManifestにApplication IDが文字列で埋め込まれており、自動的に置換されずにインストール時にエラーが出てしまう問題がありました (com.ninjinkun.njkappに対応するアプリケーションはないという感じの文句が出た記憶) 。しかし今年の4月下旬にリリースされた Manifest Manager のPlaceholder機能を使って、Android Manifestに変数が埋め込めるようになりました。以下のように設定することで、エラー問題が解決できます。

AndroidManifest.xml

<provider
    android:name="com.ninjinkun.njkapp.NJKProvider"
    android:authorities="${applicationId}"
    android:exported="false" />

Placeholderの応用

また、build.gradleで自由に変数を定義することもできます。これを使う事で、開発用アプリを別名にできます。

build.gradle

defaultConfig {
     manifestPlaceholders = [appName:"@string/app_name"]
}

productFlavors {
    staging {
        manifestPlaceholders = [appName:"@string/app_name_staging"]     
    }
    production { }
}

AndroidManifest.xml

<application
        android:name="com.ninjinkun.njkapp.Application"
        android:icon="@drawable/ic_launcher"
        android:label="${appName}" >
        <activity
            android:name="com.ninjinkun.njkapp.MainActivity"
            android:label="${appName}"
            android:launchMode="singleTop"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
      </activity>
...

category.LAUNCHAERになっているActiviyのlabelを変更しないとランチャーでのアプリ名が変わらないのに注意。AndroidManifestを自由に書き換えられるので、アプリアイコンを変えることもできると思います。

BuildConfigField

サーバーの本番/開発機を振り分けるために、以下のbuildConfigFieldを設定しています。

build.gradle

productFlavors {
    staging {
        buildConfigField "boolean", "SERVER_PRODUCTION", "false"
    }
    production {
        buildConfigField "boolean", "SERVER_PRODUCTION", "true"
    }
}

コードから使うときは以下のようにBuildConfigクラスの定数として参照できます。

public String getHost() {
    if (BuildConfig.SERVER_PRODUCTION) {
        return context.getString(R.string.host_production);
    } else {
        return context.getString(R.string.host_staging);
    }
}

まとめ

以上の設定をまとめて、仕事で使っているbuild.gradleBuild Variantsまわりはだいたい以下のようになっています (名前空間だけ変えました)。

build.gradle

defaultConfig {
     manifestPlaceholders = [appName:"@string/app_name"]
}

productFlavors {
    staging {
        setApplicationId("com.ninjinkun.njkapp.staging")
        buildConfigField "boolean", "SERVER_PRODUCTION", "false"
        manifestPlaceholders = [appName:"@string/app_name"]
    }
    production {
        buildConfigField "boolean", "SERVER_PRODUCTION", "true"
    }
}

補足: Build Variantsについて

ビルド構成を決めるBuild Variantsについては先ほどのnobuokaさんの記事に詳細な解説があるので、ここでは簡単な説明だけしておきます。

Build VariantsはProduct FlavorsBuild Typesの組み合わせで表現されます。

  • Product Flavors
    • アプリに別名を付けたり、一部を変更したりしてリリースする為の仕組み
    • Flavor毎にAndroidManifestやリソースを持つことが出来る
    • manifestPlaceholdersが使える
      • アプリ名などを変更可能
  • Build Types
    • アプリのビルド設定を切り替えるための仕組み
    • デフォルトではdebug / releaseが用意される
    • releaseでビルドすると最適化や圧縮が行われる

Product Flavorsがstaging/production、Build Typesがdebug/releaseだと以下の様に組み合わせから選べるようになります。

スクリーンショット 2014-07-22 17.25.35.png

2014/9/5 追記

manifestPlaceholdersはただの変数なので、設定を追加したい場合はハッシュの後ろに追記する必要があります。

manifestPlaceholders = [appName:"@string/app_name", gaCode:"@string/ga_code"]

【翻訳】あなたが求めていたリアクティブプログラミング入門

あなたはリアクティブプログラミングと呼ばれる新しい方法が気になっている。 勉強するのは大変で、良い教材がないのでさらに難しい。私が勉強を始めたときは、まずチュートリアルを探した。見つけたのは一握りの実践的なガイドだけ、しかもそれらは表面をなぞっているだけで、リアクティブプログラミングのアーキテクチャ全体像を構築しようとしてはいなかった。ある関数を理解するのに、ライブラリのドキュメントは役に立たないことがある。 これを見て欲しい。

Rx.Observable.prototype.flatMapLatest(selector, [thisArg])

Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element's index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.
要素のインデックスを組み込むことによって、Observable列の各要素を新しいObservable列に射影し、その後Observable列のObservable列をObservable列に変換して、最新のObservable列のみから値を生成する。

なんじゃこりゃ。

私は2冊の本を読んだが、片方は全体像のみを描き、もう片方は特定のFRPライブラリの使い方に注力していた。私は作りながら理解するという、困難な方法でリアクティブプログラミングを学習する羽目になった。Futuriceでの仕事では実際のプロジェクトで使用し、問題に当たったときは同僚にサポートしてもらった

学習の上で一番難しい部分は、FRPで考えるということだ。従来の典型的な命令型でステートフルなプログラミングから離れて、違うパラダイムで考えるように脳を強制しなくてはならない。私はこのようなガイドはインターネットで見つけられなかったので、FRPで考えるための実践的なガイドは世界にとって価値があると考えた。ライブラリのドキュメントは後から道を照らしてくれるだろう。このガイドが役に立つことを願う。

"関数型リアクティブプログラミング (FRP) とは何か?"

インターネットには良くない説明と定義が溢れている。Wikipediaは案の定一般的で理論的すぎる。 Stackoverflowの模範的解答は新人向きではない。Reactive Manifestoはプロジェクトマネージャやビジネス系の人間に見せるもののようだ。"Rx = Observables + LINQ + Schedulers" はとても重たく、我々を混乱させるマイクロソフト的なものだ。"リアクティブ" や "変更の伝搬" といった言葉は、あなたの知っている典型的なMV*や、あなたのお気に入りの言語が既にやっていることとFRPとは何が違うのかを伝えていない。もちろん私のフレーワークのビューはモデルに反応する。もちろん変更は伝搬される。そうでなければ何も描画されないはずだ。

戯言はやめにしよう。

FRPは非同期データストリームを用いるプログラミングである ( FRP is programming with asynchronous data streams)

見方によれば、これは別に新しいものではない。イベントバスやクリックイベントは本物の非同期イベントストリームで、観測したり、副作用を実行したりできる。FRPはそのようなアイデアステロイド剤なのだ。データストリームはクリックやホバーイベントだけではなく、何からでも作れる。ストリームは安価でどこにでもある。変数、ユーザー入力、プロパティ、キャッシュ、データ構造などなど何でもストリームにできる。例えば、Twitterのフィードをクリックと同じ方法でデータストリームにできると想像してみてほしい。ストリームをlistenして、それに応じて反応することができる。

その上、ストリームを合成、作成、フィルタする驚くべき関数の道具箱が手に入る。そこには "関数型" のマジックが働いている。ストリームは他のストリームの入力としても使う事ができる。複数のストリームを入力に使う事も可能だ。二つのストリームをmergeできる。あなたの興味があるイベントだけをfilterして違うストリームを得ることもできる。あるストリームからデータの値を他の新しいストリームにmapすることもできる。

ストリームがFRPの中心だとして、注意深く見ていこう。おなじみの "ボタンのクリック" イベントストリームから始めることになる。

Click event stream

ストリームとは、時間順に並んだ進行中のイベントの列だ。ストリームは、(何かの型の) 値、エラー、"完了" という3つのシグナルを発行できる。"完了" が発行される場合を考えると、例えばボタンを含んだウィンドウやビューが閉じられる場合が考えられる。

発行される3つのイベントは、値が出力された際に実行される関数、エラーが出力された際に実行される関数、'完了'が出力された際に実行される関数を定義することによって、非同期にキャプチャする。値のみに注目している場合には後の二つは割愛する場合もある。ストリームを "listening" することは subscribing と呼ばれる。この関数群をobserverと定義する。ストリームはobserveされるsubject (もしくは "observable") である。これの正確な定義はObserver Design Patternにある。

ダイアグラムを描画する他の手段としてはASCIIを使う方法があり、このチュートリアルのいくつかの部分で使っていく。

--a---b-c---d---X---|->

a, b, c, d are emitted values
X is an error
| is the 'completed' signal
---> is the timeline

既に見たものと似ているので退屈しないで欲しいのだが、新しい要素として、オリジナルのクリックイベントストリームから変換された新しいクリックイベントストリームを作るつもりだ。

始めに、何回クリックされたかを表すカウンターストリームを作ろう。普通のFRPライブラリでは、mapfilterscanなどのように、ストリームに対して使える関数が沢山用意されている。この関数群からclickStream.map(f)のように一つを適用すると、関数はクリックストリームをベースとした新しいストリームを返す。これはどんな場合でもオリジナルのクリックストリームを変更することはない。これは不変性と呼ばれる、FRPストリームにつきものな性質だ。美味しいパンケーキにシロップがつきものなようにね。これにより、clickStream.map(f).scan(g)のようにチェーンできる。

  clickStream: ---c----c--c----c------c-->
               vvvvv map(c becomes 1) vvvv
               ---1----1--1----1------1-->
               vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5-->

map(f)関数は定義した関数fに従って出力された値を (新しいストリームの中で) 置き換える。我々のケースでは、各クリックを1という数字にマップすることになる。scan(g)関数はストリームのそれまでの値をまとめ上げて、 x = g(accumulated, current) として値を返す。この例ではgは単純な加算である。そして、クリックが発生した際に、counterStreamが総クリック数を出力する。

FRPの本当の力を見せるために、"ダブルクリック" イベントのストリームについて考えよう。この話でより面白いのは、トリプルクリックのストリームについても、ダブルクリックや他の複数回クリック (2回かそれ以上) と同じように考えられることだ。息を深く吸い込んで、伝統的な命令的でステートフルな方法でどう実現するかを想像してみよう。扱いづらく、状態を保持するために幾つかの変数を必要とし、さらにタイムインターバルを操るものになると思う。

FRPのそれはもっとシンプルだ。実際、ロジックはたった4行のコードなのだ。しかし今はコードのことは置いておこう。あなたが初心者でもエキスパートでも、ストリームの構築を理解するためには、ダイアグラムで考えるのが一番だ。

Multiple clicks stream

グレーのボックスはストリームを他のストリームに変換する関数だ。まずクリックをリストに積み上げていくのだが、250ミリ秒の "イベントが無い時間" が発生する (これがbuffer(stream.throttle(250ms))のやっていることだ。ここでは詳細を理解しなくていい。ただのFRPのデモなのだ)。結果はリストのストリームなので、そこに map()を適用して、対応するリストの長さを整数にして、リストにmapする。最後にfilter(x >= 2)関数で1の場合を無視するようにする。3つの操作で欲しかったストリームが得られた。これをsubscribe ("listen")して、望むように反応する処理を書ける。

このアプローチの美しさを楽しんで欲しい。この例は氷山の一角だ。例えば、同じ操作をAPIレスポンスのストリームなど違う種類のストリームに適用できるし、他にもたくさんの関数が使える。

"なぜFRPの導入を検討するべきなのか"

FRPはコードの抽象のレベルを引き上げる。巨大な実装の詳細に常にうんざりさせられることなく、ビジネスロジックを定義するイベントの相互関係に集中できる。FRPを使ったコードはより簡潔になるだろう。

ダンなWebアプリやモバイルアプリは、データイベントに関連したUIイベントが多数あり、高度にインタラクティブなので、この利点がより明確だ。10年前、Webページのインタラクションと言えば、長いフォームをバックエンドに送って、フロントエンドはシンプルに描画するだけだった。アプリはよりリアルタイム性を持つように進化している。一つのフォームフィールドの変更は、自動的にバックエンドでの保存を引き起こす。コンテンツを "いいね" すると、リアルタイムで接続している他のユーザーに反映される。

現代のアプリは、高度にインタラクティブな体験ユーザーに与えるために、多数のリアルタイムイベントを扱っている。我々はこれを適切に取り扱うツールを探しており、リアクティブプログラミングがその答えなのだ。

例で見るFRPの考え方

現実の事柄に飛び込んでみよう。FRPでどう考えるかについてステップ・バイ・ステップでガイドしてくれる、現実世界の実例だ。模造の例では無く、中途半端なコンセプトでも無い。このチュートリアルが終わる頃には、どうしてそうするのかを理解しながら、実際に動くコードを生み出すことになるだろう。

私はツールとしてJavaScriptRxJSを選んだ。なぜなら、JavaScriptはこの時点で最もよく知られている言語で、Rx*ライブラリファミリー は多くの言語とプラットフォームで広く利用可能だからだ (.NET, JavaScala, ClojureJavaScript, Ruby, PythonC++Objective-C/Cocoa, Groovy、等々)。使うツールがなんであれ、次のチュートリアルは具体的に役立つだろう。

"Who to follow" 推薦ボックスを実装する

Twitterには、フォローするアカウントを推薦するUIエレメントがある。

Twitter Who to follow suggestions box

我々はこのコア機能を真似することに集中する。コア機能は

  • 起動時にAPIからアカウントのデータをロードし、3人の候補を表示する
  • "更新" が押されたら、3人の他の候補をロードして、3つの行に挿入する
  • 'x'ボタンが押されたら、その候補だけをクローズして別の候補を表示する
  • それぞれの行は候補アバターを表示して、彼らのページにリンクする

となる。他の機能とボタンは、重要でないので考えないことにする。そしてTwitterの代わりに、似たようなAPIで認証なしで使えるもの使う。GithubのユーザーをフォローするUIを作ろう。ユーザーを取得するGithub APIがあるのだ。

先に試してみたければ、完全なコードは http://jsfiddle.net/staltz/8jFJH/48/ にある。

リクエストとレスポンス

この問題にどうやってFRPで取り組むのか? そう、これから始めよう。全てがストリームにできる。これがFRPのマントラだ。簡単な機能から始めよう。"起動時にAPIから3人のアカウントデータをロードする"。特別なものは何もない。単純に (1) リクエストを発行し (2) レスポンスを受けとり (3) レスポンスを描画する。ではリクエストをストリームとして表現してみよう。初めの方では、これはやり過ぎだと感じるかもしれないが、我々はまず基本から始めたいと思う。いいね?

最初は1つのリクエストからスタートする。これを1つのデータストリームとしてモデル化すると、1つの出力される値を持つストリームとなる。後でまた複数のリクエストを取り扱うが、まずは1つだけだ。

--a------|->

Where a is the string 'https://api.github.com/users'

これがリクエストしたいURLのストリームだ。リクエストイベントが発生すると、このストリームはいつ実行するかと、何をリクエストするかを問い合わせてくる。いつ実行するかは、イベントが発行された時だ。そして何をリクエストするかは出力された値、すなわちURLを含む文字列だ。

単一の値のストリームを作るのは、Rx*ではとてもシンプルだ。公式な用語ではこのようなストリームは "Observable" と言い、事実これは観測可能なものだ。しかし私はこれを馬鹿げた名前だと感じるので、ストリームと呼ぶ。

var requestStream = Rx.Observable.returnValue('https://api.github.com/users');

しかしこのままではただの文字列のストリームなので、値が出力された時に何かしなければならない。これはストリームをsubscribingすることで実現できる。

非同期リクエストをハンドリングするために、jQuery Ajax callbackを使っていることに注意して欲しい。しかしちょっと待ってもらいたい。FRP非同期データストリームを取り扱うものだ。リクエストに対するレスポンスもストリームにできるのではないか?そう、できる。コンセプトレベルではこのようになる。さあやってみよう。

requestStream.subscribe(function(requestUrl) {
  // execute the request
  var responseStream = Rx.Observable.create(function (observer) {
    jQuery.getJSON(requestUrl)
    .done(function(response) { observer.onNext(response); })
    .fail(function(jqXHR, status, error) { observer.onError(error); })
    .always(function() { observer.onCompleted(); });
  });

  responseStream.subscribe(function(response) {
    // do something with the response
  });
}

Rx.Observable.create() は、observer (他の言葉で言えば "subscriber") にデータイベント (onNext()) やエラー (onError()) の情報を伝えることによって、カスタムストリームを作り出す。これはjQuery Ajax Promiseをラップしているだけではないのか。もしかしてPromiseはObservableという意味なのか?

         

Amazed

イエス。

ObservableはPromise++だ。Rxではvar stream = Rx.Observable.fromPromise(promise)のように、Promiseを簡単にObservableに変換できる。唯一の違いは、ObservableはPromises/A+規約に則っていないことだが、コンセプトレベルでは衝突していない。Promiseは一つの発行される値のみを扱う、シンプルなObservableなのだ。FRPストリームはpromiseの枠を超えて、複数の返値を扱うことができる。

これは素晴らしいことだ。そしてFRPが少なくともPromiseと同じくらい強力であることを示している。もしあなたがPromise厨なら、FRPにできることをよく見ておくといい。

元の例に戻ろう。気がつきやすい人ならsubscribe()コールを他の内部でも使うと、コールバック地獄と同じことが起こるのに気づくだろう。responseStreamの生成もまたrequestStreamに依存している。前に聞いたように、FRPは変換したり新しいストリームを生成するシンプルなメカニズムを持っているので、そのようにしてみよう。

基本的な関数の中で今最も知るべきなのはmap(f)だ。これはstream Aから値を受けとって、f()を適用して、stream Bの値を生み出す。リクエストとレスポンスに対して行おうとするなら、リクエストURLを (ストリームに偽装した) レスポンスPromiseにmapできる。

var responseMetastream = requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

そして我々は "メタストリーム" と呼ばれる獣、ストリームのストリームを作り出すことになる。慌ててはいけない。メタストリームは出力された値それぞれが別のストリームになっている。これはポインターとして考えればよい。出力された値それぞれが別のストリームへのポインターなのだ。この例では、リクエストURLそれぞれが、対応するレスポンスを含むpromiseストリームにmapされている。

Response metastream

レスポンスに対するメタストリームはややこしくて、役に立たないように見える。欲しいのはレスポンスに対するシンプルなストリームで、JSONに対する'Promise'等ではなく、出力される値がJSONオブジェクトになっているものだ。ここでミスターFlatmapにご登場願おう。これはメタストリームを "平滑化する" ようなmap()の変形で、"幹" のストリームで出力されたすべてを、"枝" のストリームで出力されるようにする。メタストリームはバグなどではないし、Flatmapもそれを取り繕って修正するものではない。FRPで非同期レスポンスを扱うためのツールなのだ。

var responseStream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

Response stream

いいね。レスポンスストリームがリクエストストリームに対応して定義されているので、もしこの後で複数のイベントがリクエストストリームで発生しても、レスポンスストリームでは対応したレスポンスイベントが、期待通りに発生する。

requestStream:  --a-----b--c------------|->
responseStream: -----A--------B-----C---|->

(lowercase is a request, uppercase is its response)

ついにレスポンスストリームを手に入れた。受けとったデータを描画しよう。

responseStream.subscribe(function(response) {
  // render `response` to the DOM however you wish
});

今までのコードを繋げるとこうなる。

var requestStream = Rx.Observable.returnValue('https://api.github.com/users');

var responseStream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseStream.subscribe(function(response) {
  // render `response` to the DOM however you wish
});

更新ボタン

まだレスポンスJSONには100人のユーザーが含まれていることを伝えていなかった。APIはページオフセットのみを許可していて、ページサイズは指定できないので、我々は3つだけデータオブジェクトを使って、残りの97個は捨てることになる。この問題については今は目をつぶろう。後でレスポンスをキャッシュする方法についても見ていく。

更新ボタンがクリックされる度、リクエストストリームは新しいURLを出力して、新しいレスポンスが得られる。このためには2つのことが必要だ。それは、更新ボタンのクリックイベントのストリーム (マントラ: 全てがストリームにできる) と、更新クリックストリームによってリクエストストリームを変更することだ。喜ばしいことに、RxJSはイベントリスナーからObservableを生成するツールがある。

var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');

更新クリックイベントは、それ自体がAPI URLを運んでくれるわけでない。クリックを実際のURLにmapする必要がある。リクエストストリームを変更して、これを更新クリックストリームにする。これは毎回ランダムなオフセットパラメーターが付与されたAPIエンドポイントを返すようにmapされている。

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

私は愚かで自動テストもしていなかったので、前に作った機能を壊してしまった。起動時には何のリクエストも発生せず、更新ボタンをクリックしたときだけ発生するようになってしまった。ううむ。更新がクリックされた際のリクエストと、Webページが開かれた際のリクエスト、両方の振る舞いが必要だ。

それぞれのケースに対応した、別々のストリームを作る方法は既に知っている。

var requestOnRefreshStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

var startupRequestStream = Rx.Observable.returnValue('https://api.github.com/users');

しかしこの2つどのように1つに "マージ" すれば良いのだろう?そう、merge()があるのだ。ダイアグラム表現で説明するとこのようになる。

stream A: ---a--------e-----o----->
stream B: -----B---C-----D-------->
          vvvvvvvvv merge vvvvvvvvv
          ---a-B---C--e--D--o----->

今やこれは簡単にできる。

var requestOnRefreshStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

var startupRequestStream = Rx.Observable.returnValue('https://api.github.com/users');

var requestStream = Rx.Observable.merge(
  requestOnRefreshStream, startupRequestStream
);

中間ストリームを抜きにして、違った方法でより綺麗な書き方だと、このようになる。

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  })
  .merge(Rx.Observable.returnValue('https://api.github.com/users'));

もっと短くて読みやすくするとこうだ。

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  })
  .startWith('https://api.github.com/users');

startWith() 関数はあなたが思っている通りの関数だ。入力ストリームが何であれ、startWith(x)が実行された出力ストリームは、最初はxから始まる。私はまだ充分にDRYではなく、APIエンドポイントの文字列を繰り返してしまっている。これを修正する一つの方法は、startWith()refreshClickStreamの近くに動かして、更新クリックを起動時に"エミューレート"することだ。

var requestStream = refreshClickStream.startWith('startup click')
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

いいね。"自動テストを壊してしまった" ところまで戻って考えると、最後のアプローチとの唯一の違いは、startWith()を追加したことだとわかるはずだ。

3つの候補をストリームでモデリングする

これまで、responseStreamのsubscribe()した際に描画する推薦 UIエレメントのみ扱ってきた。今更新ボタンについて考えてみると、問題がある。それは'更新'ボタンをクリックしてすぐには、現在の3つの候補が消えないことだ。新しい候補はレスポンスが到着してからやって来る。しかしUIを良い感じにするためには、現在の候補を更新が押された時に消す必要がある。

refreshClickStream.subscribe(function() {
  // clear the 3 suggestion DOM elements
});

違う。急いではいけないよ、相棒。これは良くない。なぜなら、候補DOMエレメントに影響する (他の1つもresponseStream.subscribe()を実行しているため)、2つのsubscriberがあり、これは関心の分離に反しているように見える。FRPのマントラを覚えているかい?

       

Mantra

出力される値が候補データを含むJSONオブジェクトのストリームになるように、候補をモデル化してみよう。これを3つの候補それぞれに行う。候補のストリーム #1 はこんな感じだ。

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // get one random user from the list
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  });

他のsuggestion2Streamsuggestion3Streamは単純にsuggestion1Streamをコピペすれば良い。これはDRYではないが、このチュートリアルの例をシンプルに保つためと、こういうケースで繰り返しをどう避けるかを考える良い問題になると思うので、このままにしておく。

responseStreamのsubscribe()で描画する方法に代わって、こうやるようにする。

suggestion1Stream.subscribe(function(suggestion) {
  // render the 1st suggestion to the DOM
});

"更新の際に候補を消す" というところに戻ると、シンプルに更新クリックを'null'候補データにmapすれば良く、suggestion1Streamに含めるとこのようになる。

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // get one random user from the list
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  })
  .merge(
    refreshClickStream.map(function(){ return null; })
  );

そして描画の際には、nullを "データが無い" ものとして実装し、UIエレメントを非表示にする。

suggestion1Stream.subscribe(function(suggestion) {
  if (suggestion === null) {
    // hide the first suggestion DOM element
  }
  else {
    // show the first suggestion DOM element
    // and render the data
  }
});

今の全体像はこうだ。

refreshClickStream: ----------o--------o---->
     requestStream: -r--------r--------r---->
    responseStream: ----R---------R------R-->
 suggestion1Stream: ----s-----N---s----N-s-->
 suggestion2Stream: ----q-----N---q----N-q-->
 suggestion3Stream: ----t-----N---t----N-t-->

Nnullを表している。

おまけとして、起動時に "空の" 候補を表示してみたい。これは候補ストリームにstartWith(null)を追加することで実現できる。

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // get one random user from the list
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  })
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);

その結果はこうなる。

refreshClickStream: ----------o---------o---->
     requestStream: -r--------r---------r---->
    responseStream: ----R----------R------R-->
 suggestion1Stream: -N--s-----N----s----N-s-->
 suggestion2Stream: -N--q-----N----q----N-q-->
 suggestion3Stream: -N--t-----N----t----N-t-->

候補のクローズとレスポンスキャッシュの使用

まだ実装していない機能が一つ残っていた。それぞれの候補は、自身をクローズして他の候補を同じ位置に表示するための、'x'ボタンを持っている。まず、どのクローズボタンがクリックされても新しいリクエストが作られるものを考える。

var close1Button = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');
// and the same for close2Button and close3Button

var requestStream = refreshClickStream.startWith('startup click')
  .merge(close1ClickStream) // we added this
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

これは動かない。これは1つをクリックしただけなのに、クローズして全ての候補を再読み込みする。この問題を解決する方法は色々あるが、面白さを保つためにも、先ほどのレスポンスを再利用して解決してみる。APIレスポンスのページサイズは100人のユーザー分あるが、我々は3人分しか使っていない。そこにはまだ豊富な新しいデータがある。追加のリクエストをする必要は無い。

もう一度、ストリームで考えてみよう。'close1'クリックイベントが発生したとき、レスポンスのリストの中から1人のランダムなユーザーを得るため、このようにresponseStream最も最近出力されたレスポンスを使いたい。

    requestStream: --r--------------->
   responseStream: ------R----------->
close1ClickStream: ------------c----->
suggestion1Stream: ------s-----s----->

Rx*ではcombineLatestという、ストリームを組み合わせるための関数がある。これこそ我々が欲しかったもののようだ。これは2つのストリームAとBを入力として受けとり、どちらかのストリームが値を出力すると、combineLatestabから最も最近出力された2つの値を合わせて、定義した関数fを適用して1つの値c = f(x,y)を出力する。これはダイアグラムで説明するのが良い。

stream A: --a-----------e--------i-------->
stream B: -----b----c--------d-------q---->
          vvvvvvvv combineLatest(f) vvvvvvv
          ----AB---AC--EC---ED--ID--IQ---->

where f is the uppercase function

combineLatest()をclose1ClickStreamresponseStreamに適用して、close 1ボタンがクリックされた時、suggestion1Streamの最新のレスポンス出力を受けとって新しい値を生成するようにする。一方combineLatest()は対称性を持っている。それは、responseStreamから新しいレスポンスが出力されたとき、combineLatest()は最新の'close 1'クリックと新しい候補の生成を結びつけるというものだ。これは面白い。なぜならこのように先ほどのsuggestion1Streamのコードもシンプルになるからだ。

var suggestion1Stream = close1ClickStream
  .combineLatest(responseStream,
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);

パズルの中で1ピースだけ欠けているものがある。combineLatest()は2つのソースの最新の値を使う。しかし片方のソースがまだ値を出力していなかったら、combineLatest()は出力ストリームにデータイベントを生成できなくなってしまう。上記のASCIIダイアグラムを見れば、1つ目のストリームが値aを出力した時点では、出力は何もないことがわかるだろう。2つ目のストリームがbを出力したときのみ、出力の値が生成されるのだ。

この問題を解決するためには異なる方法がある。そして最もシンプルなものを選んでいきたい。それは、起動時に'close 1'ボタンのクリックをシミュレートすることだ。

まとめ

これでやり終えた。完全なコードはこのようになった。

var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');

var closeButton1 = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');
// and the same logic for close2 and close3

var requestStream = refreshClickStream.startWith('startup click')
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

var responseStream = requestStream
  .flatMap(function (requestUrl) {
    return Rx.Observable.fromPromise($.ajax({url: requestUrl}));
  });

var suggestion1Stream = close1ClickStream.startWith('startup click')
  .combineLatest(responseStream,
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);
// and the same logic for suggestion2Stream and suggestion3Stream

suggestion1Stream.subscribe(function(suggestion) {
  if (suggestion === null) {
    // hide the first suggestion DOM element
  }
  else {
    // show the first suggestion DOM element
    // and render the data
  }
});

動作する例はここで見ることができる http://jsfiddle.net/staltz/8jFJH/48/

このコードのかけらは小さいが、密度は高い。複数のイベントを、関心の分離とレスポンスキャッシュによって管理しているのが特徴だ。関数型のスタイルによって、コードは命令的ではなく、より宣言的なものになる。実行する命令列を与えるのではなく、ストリーム間の関係を定義することにより、これは何であるかを伝えるだけで良くなる。例えば、FRPではコンピューターにsuggestion1Streamは'close 1'ストリームと最新のレスポンスから、1人のユーザーを組み合わせるように伝える。再読み込み時とプログラムの起動時にはnullが入る。

さらに印象的なのは、ifforwhileなどの制御フローの要素と、JavaScriptアプリケーションでよく見られる典型的なコールバックベースの制御フローが存在しないのだ。subscribe()の中では、ifelsefilter()を使って取り除くことができる (実装の詳細は課題とするので省く)。FRPには、mapfilterscanmergecombineLatest, startWithや、イベント駆動プログラムのフローを制御する、多数のストリーム関数がある。このツールセットは、少ないコードでより多くのパワーを与えてくれる。

次に来るもの

もしRx*がリアクティブプログラミングに適したライブラリと考えるなら、ぜひ時間をとって、Observableを変換、組み合わせ、生成するために、この長い関数のリストに精通して欲しい。ストリームのダイアグラムでこれらの関数を学びたいなら、マーブルダイアグラムを使ったRxJavaのとても有益なドキュメントを見るといい。どの様にすれば良いのかわからなくなったときは、ダイアグラムを描いて、その上で考え、関数の長いリストを眺め、また考えるのだ。このワークフローは私の経験上効果的だった。

Rxでのプログラミングのコツを理解したければ、Cold vs Hot Observablesのコンセプトの理解が絶対に欠かせない。これは無視しても戻ってきて、容赦なく噛みつくだろう。あなたは警告を受けたのだ。本当の関数型プログラミングを学んでスキルを磨き、Rx*で影響の出る副作用などの問題に精通することだ。

しかし関数型リアクティブプログラミング(FRP)はRxそのものではない。直感的に動き、Rxで直面した変な挙動を取り除いたBacon.jsというものがある。Elm言語は独自の世界を築いており、これはJavaScript+HTML+CSSコンパイルでき、タイムトラベリングデバッガーを備えているFRP言語なのだ。めっちゃ凄いね。

FRPはイベントだらけのフロントエンドとアプリでとても有用だ。しかしこれはクライアントサイドの話で、FRPはバックエンドとデータベース周りでも有用だ。実際、RxJavaはNetflixのAPIで、サーバーサイドの平行処理を可能にするキーコンポーネントになっている。FRPは特定のアプリケーションや言語に制限されるフレームワークではない。これは実のところ、イベント駆動なソフトウェアのプログラミングならどこでも使えるパラダイムなのだ。

もしこのチュートリアルが役に立ったら、Tweetしてみてくれ

         

staltz commented on Jul 1

関数型リアクティブプログラミングリアクティブプログラミングという言葉の周辺にはたくさんの混乱がある。[1][2]

すまない、私の落ち度だ。この種の混乱は新しいコンピューティングのパラダイムでは容易に起こり得ると思っている。このチュートリアルで "FRP" が出現している所は全て "RP" に置き換えて欲しい。関数型リアクティブプログラミングはリアクティブプログラミングの派生で、参照透過性と純粋関数型の追求ような関数型プログラミングの原則に従っているものだ。他の人々が私より良い説明をしてくれている。[3][4][5]

CourseraでScalaの講義を受けた

オンライン講義サイトのCourseraでFunctional Programming Principles in Scalaを受講して、先月にようやく終わりました。以下だらだらと感想です。

  • 一回の講義が4,5本のビデオに分割されており、1本10分くらいなのでちまちま進められる
    • 会社の昼休みに見ていた
    • iOS, AndroidアプリがあってビデオはDLして持ち運び可能
  • コースの内容
  • 課題はコードをsubmitするとテストが実行されて結果が返る形式で、プログラミングコンテストぽかった
  • 課題で困ったらディスカッションボードを見る
    • 色んな人が教えあっていて、その中で検索するとヒントにたどり着けることが多い
    • 自分は人に教えたりはできなかったけど、一応お礼だけは書いておいた
  • 楽しい、好奇心が満たされる、意識が高まる
  • 受講してもいきなりScalaで仕事だ!とはならなかった
  • プログラミングの話なので、英語の聞き取りはそんなに難しくない。WWDCのセッションくらい
  • 課題が解けないと悔しい、ひたすら課題に熱中してしまって中毒っぽくなる

経緯としては、春ぐらいにリアクティブプログラミングが気になって調べていたら、昨年Principles of Reactive Programming が開講していたのを見つけて、とりあえずビデオだけ見ていました。そんな矢先に、4月から同じMartin Odersky先生 (Scala作者) のFunctional Programming Principles in Scalaが開講されてたので、何かの足しになるかもと思って始めました。

全て無料で受講できるのですが、なんとなくモチベーションを上げるためにSignatureTrackというのに課金 ($49) してみました。結果としては、こういう履修証明書PDFがもらえるのと、シェア用のページが発行されて、それをLinkedInにリンクできるという代物でした。あまり現実的な意味は無さそうですが、とりあえず履修証明書がかっこいいので自己満足 (これを貼りたい為にブログ書いた)。

一時期はCourseraの課題のことしか考えられなくなってしまい、色々支障をきたしそうだったので、Scalaの講義以降はしばらくお休みしようと思っていました。 しかしそんな矢先に、また五十嵐先生の面白そうな講義が始まってしまいましたね…。

Interactive Computer Graphics | Coursera

ReactiveCocoa勉強会関西を開催しました #rac_kansai

先週の土曜日にReactiveCocoa (以下RAC)というOSX / iOSで使うリアクティブプログラミング(RP)フレームワーク勉強会を開催しました。

当日の資料

当日の資料のうち、見つけられたものは以下にまとめました。

はじめてのReacitveCocoa

@tinpayさん

https://github.com/tinpay/RACWarikan

ReactiveCocoa勉強会に参加してきました #rac_kansai .

ゆるやかなReactiveCocoaの導入

@ninjinkun

実践!Twitter API+ReactiveCocoa

@atsusy さん

var RAC3 = ReactiveCocoa + Swift

@ikesyo さん

RAC用クラス拡張の作り方

@yonekawa さん

https://github.com/yonekawa/RACSupportExamples

モバイルアプリのObserverパターン

@cockscomb さん

モバイルアプリのObserverパターン - cockscomblog?

ReactiveCocoaでのフロー制御の例

@matuyuji さん

Safx: IIJmioのiOSサンプルアプリのネットワーク周りのシグナル処理について

感想

個人的には、3.0になってSwiftで再実装される話は聞いておいて良かったです。またRACSignalをきちんと使うとフロー制御が綺麗に書けるという、 @matuyuji さんの発表も印象的でした。発表も多様で、全体的にバランスが良かったと思います。また @ikesyoさんにhotとcoldの違いなど、気になっていた部分を直接聞けたのも良かったです。

おわりに

RACには前から興味を持っており、仕事で使っていたのですが、RPは自分も経験が無いパラダイムなので、なかなか全体を理解できずにいました。そんな折にたまたまCocoa勉強会関西の懇親会でRACのコミッタの@ikesyoさん(京都在住)と出会ったのがきっかけで、またその場でご一緒したChatworkの@tinpayさんもお仕事でRACユーザーということで、勉強会を開催しようと盛り上がって開催したという経緯です。

RAC単体の勉強会、しかも京都開催だったのですが、東京からもchatworkの方、freeeの方が来られたりして割と盛況だったのではないかと思います。運営の@tinpayさん、Cocoa勉強会関西の @studioshinさん、会場と飲み物を提供してくださったはてなさんありがとうございました。

また東京でもRACの勉強会をやりたいねという話もしているので、もしかしたら東京開催もあるかもしれません。