関数型言語を学ぶことは実務でどう役に立ったか

Sun May 11, 2014

関数型LT大会で「実社会の問題を解決する関数型言語」というタイトルで発表しました。

というのも、会社で「すごいHaskellたのしく学ぼう!」の輪読会をしていて、最初こそ10人以上の人が参加していたのだけど、章が進むごとにどんどん人が離脱していって、主催者としてはなんとか完走したいという思いがあったので、調べたのですが、

ヒアリングから、この二つの線がクロスしたときに、人は離脱するという知見が得られました。

ということで、Haskellに対して実用性を見出したいと思いながら半年を過ごしたのですが、実用的 = 仕事で使うということであれば、今の現場でHaskellに移行するのは現実的ではありません。 でも、Haskellには関数型言語のエッセンスが詰まっていて学びが多かったと思っていて、直接的には使っていないけど、概念として役立つことがあると思ったので、それを伝えるために今回文章に起こしました。

実用性と言ったけど、何が実用的かは立場によって異なっていて、ここでの実用性とは一般的なWeb企業の話です。

NetflixのAPIの最適化

NetflixのAPIの最適化についての記事(Optimizing the Netflix API)がまさにそれで、多くのWeb企業でREST APIのクライアントから見たときの効率の悪さには頭を悩ましていると思うのだけど、それをいかにして最適化したのかと、後日その記事のフォローで、具体的なサーバの実装について解説した記事(Functional Reactive in the Netflix API with RxJava)がまさに好例だなと思って、この話をしました。

APIの最適化のために、スレッドセーフにしつつ時には同期させたりしながら並列でリクエストを送る低レベルなスレッド操作を、シンプルに表現できるようにする必要があって、そこでNetflixではFRP(Functional Reactive Programming)のアプローチを取っていました。

FRPとは

Behaviorの例

  • マウスの座標
  • 日照によって変化するボンネットの温度
  • 振り子運動をしているブランコの速度
  • 刻々と変化する株価

Eventの例(時間と値が組になったストリーム)

  • キーを押した時刻tと、どのキーかを表すcを組にした(t, c)
  • マウスをクリックするごとに発生するEvent
  • ボンネットの温度が5度上がる毎に発生するEvent
  • ブランコの速度が0になるたびに発生するEvent
  • 株式市場で取引の開始・終了を知らせるEvent

応用先

  • イベントドリブンなコードで処理がぶつ切りになったりコールバック地獄になるのを避ける
  • アニメーションやコンピュータミュージックのシグナル処理を自然に書けるようにする
  • LINQ
  • Excel

Haskell界では10年以上前からFRPに関する研究が続けられているようです。 実装ではMicrosoftがOSSで開発しているRx(Reactive Extensions)が有名で、そのNetflixがそのJava版であるRxJavaを開発しています。

複雑なGUIの世界を完結に表現したい

今度はサーバ側ではなく、クライアントでの具体的な実装例を紹介します。 下の画像はシンプルなメッセージングアプリのスクリーンショットです。

仕様を書き出すと、次のようになります。

一見シンプルに見えるアプリですが、以外と複雑ですね。

画面内の見えている要素はどこかの要素の状態に依存していて、しかもアプリやシステムが非同期で各要素の状態を更新する、ということはアプリではよくあります。

そのため、画面のある一部を変更したいだけなのに依存性を検証してあちこちのイベントを貼り直したりして、そして保守しにくいコードへ…という問題が起こります。

FRPのライブラリの多くは、MicrosoftのReactive Extensionsから派生していますが、RxではBehaiviorはObservableというクラスで表現されています。

Instead of blocking APIs ...

class VideoService {
    def VideoList getPersonalizedListOfMovies(userId);
    def VideoBookmark getBookmark(userId, videoId);
    def VideoRating getRating(userId, videoId);
    def VideoMetadata getMetadata(videoId);
}

... create Observable APIs:

class VideoService {
    def Observable<VideoList> getPersonalizedListOfMovies(userId);
    def Observable<VideoBookmark> getBookmark(userId, videoId);
    def Observable<VideoRating> getRating(userId, videoId);
    def Observable<VideoMetadata> getMetadata(videoId);
}

Observableは値を依存グラフとして扱えるように、値をHigher OrderにLiftしたもので、イベント時にObservableの値に対して関数を適用して次々に変化させていきます。

FRPを先ほどのメッセージングアプリに適用すると、コードは以下のようになります。(RxJava + retrolambda + Lombok)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_compose_message);
    ButterKnife.inject(this);

    setupUiUsingRx();
}

private void setupUsingRx() {
    val phoneNumberText = Events.text(phoneNumberEditText);
    val messageBodyText = Events.text(messageBodyEditText);
    val sendMessageClick = Events.click(sendMessageButton);

    messageBodyText
            .map(text -> !text.trim().equals(""))
            .subscribe(Properties.enabledFrom(sendMessageButton));

    messageBodyText
            .map(text -> MAX_BODY_LENGTH - text.length())
            .map(remainingChars -> getString(
                    R.string.remaining_characters_text,
                    remainingChars,
                    MAX_BODY_LENGTH))
            .subscribe(Properties.textFrom(remainingCharactersTextView));

    sendMessageClick
            .flatMap(o -> Observable.combineLatest(
                    phoneNumberText,
                    messageBodyText,
                    Message::new)
            .take(1))
            .subscribe(message -> {
                if (message.getPhoneNumber().trim().equals("")) {
                    phoneNumberEditText.requestFocus();
                } else {
                    messageBodyEditText.setText("");
                    messageListAdapter.add(message.getMessageBody());
                }
            });

    messageListAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
    messageListView.setAdapter(messageListAdapter);
}

振る舞いに着目して、Observableがどのように変化していくかという流れを記述するようになりました。

ところで、FRPをかなり端折って説明したのですが、たとえばRxの説明だと「モナド」という言葉が何度も出てきて、「モナド」を理解するために調べるとHaskellのコードが出てきて意味不明だよねみたいなことが書かれていますが、すごいH本を読み終わったあとだと、「ああモナドね、自己関手の圏におけるモノイド対象でしょ」(ミサワ顔)となります。

関数型言語を勉強して、ただちに役に立つかどうかは分かりませんが、問題解決に対して今まで見えなかったアプローチが見えるようになったというでは良かったと思います。

まとめ

{% oembed https://twitter.com/rejasupotaro/status/465329077256069122 %}

イベントにたくさん人が集まったり、入門していることを前提に再入門の特集が組まれていたりして、関数型言語の浸透具合で感じました。

輪読会を継続的に行うのは結構大変だったのですが、学びは多かったしLT大会楽しかったし、やって良かったなと思いました。



  « Previous: Next: »