この記事はDMMグループ'20卒内定者アドベントカレンダー15日目の記事です.
現在修士2年,プログラミング言語の研究室に所属していて関数リアクティブプログラミング (Functional Reactive Programming, FRP) というプログラミングパラダイムの研究をしています.修論も佳境!終わらない実装!ということで自己紹介がてらFRPの簡単な紹介でも書こうかなと思います.
関数リアクティブプログラミング(FRP)はリアクティブなソフトウェアの記述をサポートするプログラミングパラダイムです.
アプリケーションのUIや制御システムなど,外部からの入力に応答するようなソフトウェアの場合にはキー入力やセンサーの値などの非同期的な入力を処理する必要があります.非同期的な入力に対する処理はポーリング・コールバック関数・マルチスレッディングなどで実装されるわけですが,これらが組み合わさってくると実行の流れが把握しづらくなり,プログラムが複雑になってしまいます.
例えば,テキストフィールドを持つUIの実装を考えてみましょう.入力の変化を検知して,バリデーションかけて,オートサジェスチョンのために外部にリクエストを投げて,状況に応じてUIを無効にして…と機能が追加されて処理が組み合わさっていくと複雑さがどんどん増していきます.
FRPはそんなリアクティブなソフトウェアにおける入力や内部状態を時間変化する値である「時変値」という概念で扱います.時変値同士を関数やコンビネータで組み合わせることでプログラムの内部状態を宣言的に表現することができます.
例えば,テキストフィールドの中身を時変値だと見なせば,その時変値からバリデーションの現在のステータスを表す時変値が定義できます.さらに,この時変値を使えば「正しい入力のときのみ有効になるボタン」という時間変化するコンポーネントを記述できるようになるわけです.内部状態そのものが時変値の形で宣言的に定義されることでプログラムの見通しがよくなり,拡張性が高くなるというのがFRPの利点です.
FRPのオリジナルはFunctional Reactive Animationにて提案された「Fran」というHaskellのライブラリで,僕はこの言語のチュートリアルページ が妙にシュールで大好きなのですが,せっかくなのでWebと関連がある実例を紹介してみようと思います.
以前まではFRP + WebといえばElmだったんですが残念ながら"A Farewell to FRP"してしまいましたね…
少しだけ調べてみるとBacon.jsというライブラリを見つけました.「チュートリアルを見てね!」では味気ないので少しだけ紹介してみようと思います.チュートリアルの冒頭では先程挙げたようなテキストフィールドのバリデーションの例が紹介されていました.ユーザ名と氏名を入力して,両方とも入力されているときに登録ボタンを有効にするという例を考えます.
Bacon.jsには以下の二種類の時変値が用意されています.
- イベントストリーム : 離散的な値のストリーム
- プロパティ: 連続的に時間変化する値
これらを組み合わせてプログラムの記述を行います.テキストフィールドの中身を表すプロパティは(jQueryと組み合わせると)以下のように書けます.
function textFieldValue(textField) {
function value() { return textField.val() }
return textField.asEventStream("keyup").map(value).toProperty("")
}
username = textFieldValue($("#username input"))
fullname = textFieldValue($("#fullname input"))
textFieldValue()
はテキストフィールドから中身のプロパティを作る関数です.textField.asEventStream("keyup")
でkeyup
イベントのストリームを取得し,map()
で中身のストリームに変換しています.このようにして入力が書き換わるたびにテキストフィールドの中身がイベントとして発火するイベントストリームを定義できます.あとはこれをtoProperty()
を使ってプロパティに変換しています.このメソッドは引数に渡された値を初期値に持ち,イベントを検知するたびに値が更新されるようなプロパティを返します.
一方,これらのプロパティをつかって入力のバリデーションは以下のように書けます.
function nonEmpty(x) { return x.length > 0 }
usernameEntered = username.map(nonEmpty)
fullnameEntered = fullname.map(nonEmpty)
buttonEnabled = usernameEntered.and(fullnameEntered)
usename
とfullname
をmap()
で写像してフィールドが空でないかのプロパティに変換し,それらの論理積を取っています.
最後はこのプロパティをコンポーネントに反映する部分です.以下のように書けばよいです.
buttonEnabled.not().onValue($("#register button"), "attr", "disabled")
onValue()
はプロパティの値の変化に合わせて関数呼び出しを行うメソッドです.ここでは登録ボタンのattr
メソッドを,"disabled"
とプロパティの値を引数にして(つまり引数の最後にプロパティの値を追加して)呼んでいます.これでbuttonEnabled
の否定を取ってdisabled
属性に反映することができます.
こんな感じで「テキストフィールドの入力が空でないか」といった内部状態を式の形で綺麗に表現でき,それを操作してUIに反映させることができます.機能の追加のために新しいプロパティを定義する際も他のプロパティをそのまま再利用することができます.
ReactiveX (Reactive Extensions, Rx) はイベントストリームを組み合わせて非同期処理を記述するライブラリです.FRPの話になるとRxが話題に上がることがありますが,Rxのイントロダクションには「RxとFRPは別物」で,「主な違いとしてFRPは連続的に変化する値を操作するがRxは離散的な値を操作する点が挙げられる」とあります.
Franを提案したConal Elliott本人もFRPのキーとなる特徴として "having a precise and simple denotation"(正確でシンプルな表記を持つこと)と"continuous time"(連続的な時間の概念)を挙げています(参照:The essence and origins of FRP).
考え方が似た部分も多いですが,正確には別のものだと考えるべきなようです.
僕の研究はそんなFRPを使ってマイコンなどをターゲットにした小規模組込みシステムの開発をサポートしてあげようという話です.最近はその研究の言語処理系を書いたり,競技プログラミングを少しやったりしています.いや本当に研究の実装が終わらないんだよなぁ…
プログラミング言語が好きで,面白い言語機構とかプログラムをどうモジュール化するかみたいな話に興味があります.DMMはいろんなことに手を伸ばしているし歴史もある会社なので規模感のあるコードに触れるんじゃないかと思って応募させてもらいました.松本CTOも参入して大きく動いている時期とも聞いているのでとても楽しみにしています.
趣味の話をするとゲームが好きです.腰を落ち着けてじっくり遊ぶタイプのゲームが好きで,今年もよくPS4にはお世話になりました.SEKIROがGame Of The Yearを取りましたが,あれは本当によく考えてつくられたゲームだなぁという印象の素敵なゲームでしたね…ラスボス戦の作りとか感動しちゃった…
頻度は稀ですが,思い出したように散歩に行ったりもします.いい景色をながめてぼんやりするのが好きで,今年はひたちなか海浜公園のネモフィラを見に行きました.
同じような理由で登山するのも好きです.登山道具の購入を長年悩んで来たのですが,ついに先月15万円を溶かして一通り揃えました(服めちゃくちゃ高くない…?).この前は神奈川県丹沢の鍋割山に行ってきました.富士山がとても綺麗で,山頂で見惚れてしばらく帰れませんでした.
周りの同期を見渡してみるとみんな経験が豊富そうな人たちばかりで非常に焦っております. 入社したらバックエンドを中心にいろいろなお仕事に触れていきたいです. 研究の内容が直接活きる機会は少ないかもしれないですが,人間にやさしいコードが書けるように精進していきたいと思います.
それでは,みなさんどうかよろしくお願いします.