LoginSignup
38

More than 5 years have passed since last update.

FRPライブラリを作ってわかった、FRPの意味と意義

Posted at

FRPライブラリを一日かけて作ってて思いました。これはメインループの復権だ!!
例のごとくまさかりよろしくです!!

--

頭の体操をしましょう!!

FRPライブラリである、Bacon.jsの例題を引用します。
https://baconjs.github.io/tutorials.html

  • Username availability checking while the user is still typing the username
  • Showing feedback on unavailable username
  • Showing an AJAX indicator while this check is being performed
  • Disabling the Register button until both username and fullname have been entered
  • Disabling the Register button in case the username is unavailable
  • Disabling the Register button while the check is being performed
  • Disabling the Register button immediately when pressed to prevent double-submit
  • Showing an AJAX indicator while registration is being processed
  • Showing feedback after registration

雑に日本語に訳します。

  • usernameが使えるかどうかリアルタイムでチェック
  • usernameが使えないなら通知する
  • チェックを行っている最中インジゲータを表示
  • 登録ボタンはusernameとfullnameが入力されるまで無効化
  • usernameが使えないなら登録ボタンは無効化
  • 二重登録を避けるためにクリックされたら即座に無効化
  • 登録処理中はインジゲータを表示
  • 登録処理がおわったら通知する

んん?余計わかりにくくなった気がしますね……プログラム書きならコードで語るのだ@v@!!
CoffeeScriptっぽい疑似コードで書きましょう。

available = (name)->
    showIndicator()
    result = checkAvailableAjax(name)
    hideIndicator()
    result

processRegistration = (user, full)->
    showIndicator()
    registerAjax(user, full)
    hideIndicator()

check = ()->
    disableButton()
    unless available(@username)
        feedbackUnavailableUsername()
    else if @username and @fullname
        enableButton()
        return true
    return false

register = ()->
    disableRegisterButton()
    processRegistration(@username, @fullname)
    feedbackRegistraction()

おお、なんか簡単そうですね!!
しかし!!!非同期の魔物が待ち受けています!!これをまともに組んだらうん10倍くらいに膨れ上がるでしょう。(気になった方だけ実装してください……)

しかしです、拙作のライブラリを使えばこのようになります。

available = ($)->
  $(showIndicator)()
  result = $.async(checkAvailableAjax)($.username).get()
  $(hideIndicator)()
  result

processRegistration = ($)->
  $(showIndicator)()
  $.async(registerAjax)($.username, $.fullname).get()
  $(hideIndicator)()

form.on.keydown = ($)->
  $(disableButton)()
  unless available($)
    $(feedbackUnavailableUserName)()
  else if $.username and $.fullname
    $(enableButton)()
    return true
  return false

form.after.keydown = (value)->
    form.checkResult = value

form.on.register = ($)->
  unless $.checkResult
    return false
  $(disableButton)()
  processRegistration($)
  $(feedbackRegistration)()
  return true

なんということでしょう、疑似コードとあまり変わらないではないですか!!
あとは各々の関数に対して、実装していくだけですね。

秘密はFRP

これはBacon.jsと同じく、FRPと呼ばれるパラダイムに基づいています。
FRPの説明の前に、Reactiveであることからです。

Reactive Programming

みなさま、あるいは皆様の近くに方眼紙マスター(!?)がいらっしゃると思いますですのでご存知かとおもいますが、Excelの式は値を変更したらリアルタイムに値が書き変わります。

= E4 + E5

このセルはE4が3、E5が5ならば8になります。E5が10に書き変わったとたん、このセルは13になります。

ですが、プログラミングの世界では、この挙動は不思議です。

a = 1
b = 2
c = a + b

#同一関数のどこか遠くは慣れた場所
a = 3

このとき、cの値は3です。5にはなりませんよね。

ですが、値が書き変わったとき、データの依存関係に応じて再計算を行う、これがReactiveであるということです。

この考え方に基づいたプログラミングとRactive Programmingと呼ぶ事にします。

なんでFunctional?

ここで少し疑問が生まれます。RPはわかった、でもなんでわざわざFRPなんでしょう。
答えは簡単で、RPにおいて、副作用はいろいろ困るからです。

たとえば以下のコードを見てみましょう。

#Reactiveな関数??1
a = 1
b = Math.random()
c = a + b

#Reactiveな関数??2
a = 1
console.log b
b = 2
c = a + b

1を見てみましょう。
ここでaを書き換えたとすると、素朴に再計算してしまったら、cの値は不定です。

2はもっとたちが悪いです。
aを書き換えるたびに、bが変わっていないにも関わらず、bが出力されてしまいますね。

このように、RPにおいて副作用を持つ関数は依存関係を見た上で、特別扱いしなければならないのです。
理想を言えば、外に追い出せばいいですよね、と言う訳で、副作用と作用を厳密に切り分けられる、FPの出番になります。

リアクティブな二つの値、Behavior、Event

余談ですが重要な概念として、この変更可能な値、二つに分類すると都合が良い事がわかってるらしいです。
それはBahaviorとEventです。

Behaviorは連続的な値です。一番わかりやすいのは時間でしょうか。
Eventは離散的な値です。例えばクリックされた、という状態を、時間とともにまとめた値等です。

解説の準備は整った!!

というわけで解説です。遠いので一部だけ抜き出しますね。

form.on.register = ($)->
  unless $.checkResult
    return false
  $(disableButton)()
  processRegistration($)
  $(feedbackRegistration)()
  return true

form.on.registerはregisterというイベントを監視する関数の受け皿です。
これが FRPのエントリポイントです 。registerのイベントの値が更新されたら、直ちに実行されます。

そして、ここがポイントになってるのですが、副作用を持つ関数には、$()や、$.async()という、 特別なマークをつけます
このマークが付いた場合、その関数を実行中にリアクティブな値を参照すると、依存関係があると見なします。この情報を元にすることで、副作用の値の保持、再計算を可能になります。
このマークが付いてない場合、参照透過関数であると見なし、常時再計算を行います。
値の参照に失敗した場合(例えばイベントなのに更新されていなかった場合)例外を投げて大域脱出します。そして値が更新されたら、再度実行されます。

一度実行した副作用を持つ関数は、依存する値が更新されるまで、実際には計算しません(キャッシュを参照します)。よって、次回からは参照透過です。

あれ、どの辺がFPなの?

白状しますと、JavaScriptの記法で関数を自在に取り回すのは難しいです。
より厳密にするなら、Bacon.jsのようにならざるを得ません。

よって手続き型に見えるように書ける、そのようにしています。これが作った理由になります。

Haskellは相変わらずすごかった

Haskellのアロー記法なるもので書いたら、こんな感じになるかもしれません。(ならないかもしれません)


arr checkResult >>> arr disableButton >>> arr processRegistration >>> arr feedbackRegistration

アロー記法は、関数をつないで大きな関数を作る物ですが、アロー記法の特異点は、そのつなぎ方によって制御構造を成す事ができると言う点です。
具体的には、以下の演算によって、制御構造を成す事が可能になります。

  • A >>> B 関数をつなぐ(順次)
  • A &&& B 同一入力をA、Bに流し込む(変数)
  • A ||| B 右、左がわかる特別な型を受け取って、右ならB、左ならAに流し込む(条件分岐)
  • 最後の口を最初にする(繰り返し)

言い換えると、 関数を手続き的につなぐ 手法です。
これはPure FPであるHaskellで実現できるからうまみがあるのでしょうが(命令型言語なら普通に表現できる)、考え方は同じです。

  • 副作用を分離する
  • 関数をつないで大きな関数を紡ぐ
  • 関数の実行制御を行う

命令型の観点から

では、命令型の方から見てみましょうか。

疑似コードをちょっと眺めてもらえますか。

これはint main()の時代だと、わりと自然に書けるものでしたよね?
ですが、そんな素朴な時代は終わってます。猫も杓子もイベントドリブンです。

ところが、ライブラリを使って書いたFRPのコードを見てみましょう。
わざとそうしてるとはいえ、メインループでかけた、すっきりとした流れそのものじゃありませんか!!

これはいうなれば イベントドリブンで分断された、メインループの復権 です。
難しい理論はどうでもいいんです!!これは喜ばしい事ですよ!!

結論

関数型視点で見ると、FRPは、弱点だった離散時間を、意味を崩す事無く扱えます。
命令型視点で見ると、FRPは、ちょっとの約束事を守るだけで、メインループをこの手に取り戻せます。

FRPすごいのです!!

で、肝心のブツは……?

まだバグ残ってます(特にキャッシュ周り……)ので遊ぶ程度にしてくださいね。
書いた本人もわかりませんので解説は期待しないでください!!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38