#122 "Applicative functors: definition and syntax"を訳してみた

本記事は、Tomas Petricek氏の"Applicative functors: definition and syntax"というブログエントリを翻訳したものです。

この和訳ドキュメントは上記ブログ記事のみを日本語に翻訳したものであり、その他のページは未翻訳です。
そのため、本文中に張られたリンクには原文に遷移するものがあります。

また、原文の意味を損なわない程度に訳文を砕いている箇所があります。
誤解がないように気をつけましたが、もしかしたら元の意味とずれてしまっているところがあるかもしれないことを予めご了承ください。

本記事は原文に則り Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) の下でライセンスしています。
原文の著作権は著者である Tomas Petricek 氏に帰属します。
日本語の内容についておかしな箇所がありましたら、私までご連絡いただけると助かります。
その他、本記事についてはコメントもしくはTwitter( @gab_km )宛にお気軽にどうぞ。

アプリカティヴ・ファンクタ: 定義と構文

最近のブログ記事で、Edward Z. Yangがアプリカティヴ・ファンクタについて話しています。 [3] 彼はアプリカティヴ・ファンクタにおける同値な2つの定義について言及しています―Haskellのライブラリ(Applicative)で使われている標準の定義と、元の論文 [1] にも現れていますが、一般的には馴染みがない方の対案(Monoidal)です。

標準の定義はHaskellで標準的に使う際に完全に筋が通りますが、私は常々もう一方の定義を好んできました。Edwardはアプリカティヴ・ファンクタについて守るべき法則を説明し、可換なアプリカティヴ・ファンクタを説明するために代わりの(Monoidal)定義を使いましたが、私はこれがもっと便利なものだと考えています。

Monoidal な定義は、LINQを使ってC#でアプリカティヴ・ファンクタをコードにする [6] のに使えるトリックとしてすごくピッタリで、さらに私は、モナドを使う(F#で抽象的な計算を書く [5] ことについての私のドラフトで議論しています)のと似たようなスタイルでアプリカティヴ・ファンクタを使うコードが書けるようにするF#の構文拡張の基盤として使いました。そして私は可換なアプリカティヴ・ファンクタがもっと注目されるといいと思っています。

代わりの定義

Applicative

定義から始めましょう。Haskellでアプリカティヴ・ファンクタを定義したいなら、以下の演算子を定義する必要があります(私はF#の構文を使いますが、考え方は同じです):

val pure  : 'T -> F<'T>
val (<*>) : F<'T1 -> 'T2> -> F<'T1> -> F<'T2>

この定義はアプリカティヴ・ファンクタがHaskellにおいて何故アプリカティヴと呼ばれるかを説明し、また普通の使い方を提示してくれています。ある関数 作用をもつ(effectful)引数 a1, a2, a3 があるとき、以下のようにコンビネータを使い、関数を適用することができます:

(pure f) <*> a1 <*> a2 <*> a3

これはHaskellでプログラミングするのに本当に便利ですが、アイディアが1つの特定の表現でしか表せません。

Monoidal

(最初の定義と同値な)代わりの定義は以下の演算子(Haskellでは、 map 演算子は Functor 型クラスの一部ですが、F#は型クラスを持っていないので、すべての演算子を明示的に書きます)を使います:

val map    : ('T1 -> 'T2) -> (F<'T1> -> F<'T2>)
val unit   : F<unit>
val ( ** ) : F<'T1> -> F<'T2> -> F<'T1 * 'T2>

unit の値は前の定義の pure 関数と本質的に同じです(作用の観点から、これらは両方とも作用のない計算を作り、 pure は特定の値を含み、 unit は unit 型の値を含むというたった1つの違いがあります― map を使うことで、簡単に unit から pure に変えることができます)。

しかし、 ** 演算子(マージ(merge)と呼びます)はもっと面白いです。もし別々の作用を持つ複数の計算(または、他の標準的でない計算の性質)があるなら、値と同時に作用(性質)を結合できます。

作用やIOについて話をするとき、この演算子は単純に作用を結合します。しかし、リストをとるとき、この演算子は zip のように振る舞います。1つの解釈として、データフロー計算と過去の値を持ったリストを書いているということです。この解釈では、マージは第一引数のn個文だけ過去の値と第二引数のm個分の過去の値を結合し、結果はmin(n, m)個分のタプルになります。面白いことに、UustaluとVeneが彼らのデータフロー計算のコモナド・モデル [9] の中で、まさにこの演算子を使っているのです。このブログ記事の範囲外ですが、興味深い点であります(彼らは可換なマージ演算子を使っています)。

前のと比べた時に、このインターフェイスの典型的な使い道は何だかあべこべな感じがします。もし幾つかの計算 c1, c2, c3 があるとすると、それらを結合できて、それから結果のタプルを使って幾つかの計算を実行できます:

c1 ** c2 ** c3 |> map (fun (a1, (a2, a3)) ->
  f a1 a2 a3)

(2つの uncurry コンビネータなどと一緒に) <*> と pure に似たようなスタイルで、 map と ** を使うことができますが、2番目の定義が(ポイントフリーとは反対に)明示的なプログラミングスタイルによりピッタリくると考えているので、違うスタイルなのはわざとなのです。

おそらく別の類似点はこれかもしれません。モナドを説明する時に(そしてモナドもアプリカティヴ・ファンクタに適用します)2つの類似点があります。一つ目が値 'T をラップする「箱」として F<'T> を扱うこと、そして二つ目が 'T を生み出す「計算」として F<'T> を扱うことです。

  • Applicative な定義は「箱」のアナロジーによりピッタリきます。私たちは普通アプリカティヴなスタイルで書き、追加のコンビネータがその箱がちゃんとやってくれている事を確認します。
  • Monoidal な定義は「計算」のアナロジーがよりしっくりきます。私たちは計算を組み立て、結果を変数(a1a2, a3)にバインドし、計算を続行します。

代案の構文

ここまで、私は2つの定義の違いをざっくばらんに説明してみました。実のところ、これらの定義は同値なので、数学的に言って、違いはありません。ですが、これらは異なる直感を与え、異なる使い道を提示するものと私は強く思っています。このセクションでは、Haskellと(研究拡張の)F#にあるアプリカティヴ・ファンクタへの構文的なサポートについてお話しします。

Haskellでの成句カッコ構文

アプリカティヴ・ファンクタの原論文は、アプリカティヴ・ファンクタを使ってプログラミングをシンプルにする構文を提案しています(Sheにおける成句カッコ [2] を参照)。書くのであれば:

(| f a1 .. an |)

この構文は展開されて:

(pure f) <*> a1 <*> .. <*> an

もしあなたがアプリカティヴ・スタイルを使っているなら、これは素敵で便利な単純化です。さらに、アプリカティヴ・ファンクタは作用をもつ引数(「箱」の中の値)に関数を適用し、(その箱を扱う)作用を伝播させたいというコードを書くのに便利だ、という直感を明らかに強調します。

この構文は、他の馴染みある抽象、特に do 記法とモナド内包表記と一緒に動かすためにHaskellがサポートする書き方と完全に異なっている、というのは触れておきたいところです。

F#の計算構文

私がドンちゃん(ドン・サイム氏)と一緒に書いた最近の論文 [5] で、私は他のF#のコンピュテーション式とマッチするようなアプリカティヴ・ファンクタ用の構文を設計してみました。この論文で論じましたように、F#のコンピュテーション式はもっと広い範囲の抽象のために使われますが、モナドを使う場合、Haskellにおける do にずいぶん似たように見えます。 をモナド用のコンピュテーション・ビルダー、 f : int -> M<int> とすると、こう書けます:

m { let! a = f 42
    let! b = f a
    return a + b }

アプリカティヴ・ファンクタ用にとても似たスタイルの構文を使うことができます。 をアプリカティヴな構文用のコンピュテーション・ビルダー、 g : int -> F<int> とすると、こう書けます:

a { let! a = g 42
    and  b = g 43
    return a + b }

その論文 [5] でこの構文についてもっと詳しく知ることができますが、キーポイントは:

  • コンピュテーション式の本体({ .. }の中)には let! .. and .. and .. で構成される、並列数がたった1つの束縛ブロックを持つことができます。これはマージ演算子(**演算子)を使って結合される計算を定義します。
  • 定義された変数は相互再帰ではないので、例えば g 43 の中で変数 a を使うことはできません。これは、上記の例においてモナド用とアプリカティヴ・ファンクタ用との間で異なるところです。
  • 並列バインディング以降のコードブロックは、(if のような)他の標準的な構造と同様に、他のアプリカティヴでない let 束縛を含めることができますが、これ以上 let! は含めることができず、 return で終わらなくてはいけません。

この構文は、モナドとアプリカティヴ・ファンクタの間の違いを良い感じに示している(そして、どんなやり方でモナドがもっとパワフルになるかを説明している)と思います。アプリカティヴ・ファンクタの原論文(セクション5)を引用してみましょう:

直感的に、(モナドは)他の計算を選択するのに影響を与える計算によって値を返すことができる一方、(アプリカティヴ・ファンクタは)計算の構造を固定し、作用を繋げるのみである。

これはまさに let! .. and .. and .. 構文の制限を表しています。ある変数の値は如何なる(アプリカティヴな)計算もその計算の一部として評価されることに影響を与えることはできません。一度特定されなくてはならず、かつ常にすべて評価されています。一方、F#のモナドで、 if のあとに let! を書くことができて、(この場合、ネストされた計算が評価されているかいないかに関わらず)1つのブランチにのみもう1つの let! を持つことができます。

現実的な例

アプリカティヴなプログラミングスタイル(と成句カッコ構文)が良い意味を成す例は沢山あります。それでは、計算としてアプリカティヴ・ファンクタを扱う明示的なスタイルが意味を成す良い例もあるのでしょうか?おそらく、私が考える最良の例は `フォームレット [4] −HTMLフォームを表現する計算の型です:

let userInfo =
  form { let! name = textBox "name"
         and surname = textBox "surname"
         let combined = name + " " + surname
         let message = "Your name is " + combined
         return message }

この例は、名前と苗字を尋ねる2つのHTMLテキストボックス要素を生成するフォームを構成します。処理が走ると、フォームは"Your name is First Last"というフォームのメッセージを返します。詳細には踏み込みたくありませんが、try joinadsのWebサイト [8] で(MacとWindowsで動作する活きたHostedモードの例を含み)もっと見つけられるでしょう。

まとめ

この記事が、アプリカティヴ・ファンクタのもう1つの定義(Monoidal型クラス)が便利だという証拠をもうちょっと提供したら良いなぁと思います。Edwardが指摘した [3] ように、この定義は法則を理解するのを助けてくれます。これは私が上でF#用に提案した構文−可換なアプリカティヴ・ファンクタが let! .. and .. and .. ブロックの中で束縛の順序を付け直せるようにしてくれる−の場合に、さらに助けになります。

直感的に、型 'T の値を包んだ値として値 F<'T> を扱い、それらをまるで 'T の値のように使いたい時に、 Applicative の定義はより自然であると信じています。しかし、 'T を返す計算としてそれらを扱うなら、他の定義の方がもっと自然かもしれません。それは計算に対処できる演算子を表現し、また(私がF#で実装した)興味深い構文を導いてアプリカティヴ・ファンクタをモナドに関連づけます。

最後に、 F<'T1> * F<'T2> -> F<'T * 'T2> 型のマージ演算子(**演算子)は、色んなところに出てくるので、本当に面白いです。アプリカティヴ・ファンクタはさておき、データフローのコモナド・モデル [9] において使われ、 zip を生成するモナド内包表記(私のMonad.Readerの記事 [7] のセクション2.5を見てください)においても使われています。

参考文献

[1] Applicative Programming with Effect - C. McBride and R. Paterson

[2] Idiom brackets (She's effectful) - C. McBride

[3] Applicative functors - Edward Z. Yang

[4] The essence of form abstraction - E. Cooper, S. Lindley, P. Wadler, and J. Yallop

[5] Syntax Matters: Writing abstract computations in F# - T. Petricek and D. Syme

[6] Beyond the Monad fashion (1.): Writing idioms in LINQ - T. Petricek

[7] Fun with parallel monad comprehension (The Monad.Reader) - T. Petricek

[8] TryJoinads - Applicative computations - T. Petricek

[9] Comonadic Notions of Computation - T. Uustalu and V. Vene

トラックバックURL

#122 "Applicative functors: definition and syntax"を訳してみた へのトラックバック

まだトラックバックはありません。

#122 "Applicative functors: definition and syntax"を訳してみた へのコメント一覧

まだコメントはありません。

コメントする

#122 "Applicative functors: definition and syntax"を訳してみた にコメントする
絵文字
プロフィール
あわせて読みたい
あわせて読みたい
記事検索
Project Euler
なかのひと
アクセス解析
Coderwall
  • ライブドアブログ

トップに戻る