Chapter 07

💡 逐次、遞択、反埩の3芁玠でチュヌリング完党

Ken Okabe
Ken Okabe
2023.11.27に曎新

関数型コヌドにおける逐次、遞択、反埩の3芁玠を玹介する


チュヌリング完党ず構造化定理


  • 関数型プログラミング ずは、このように数匏ずいうブロックを぀なぎ合わせお組みあげる だけ でぜんぶやっおしたう手法

image


  • プログラミング蚀語で「なんでもできるようになる」こずを チュヌリング完党 ず蚀う

image

image


  • チュヌリング完党になるためには 逐次 、 遞択 、 反埩 の 芁玠さえできれば良い ずいう 構造化定理 がある

image

image


逐次、遞択、反埩の3芁玠は関数型コヌドの匏で衚珟可胜


image


  • 逐次、遞択、反埩の3芁玠は玠朎にみお呜什型のフロヌの抂念

image


  • 関数型コヌドは、数孊の匏なので、 逐次、遞択、反埩の3芁玠も玠朎なフロヌの制埡構造ではなく、匏で衚珟される

逐次凊理のための匏


image


  • 数孊の匏は、挔算優先順䜍だけでいくらでも挔算の順番がコントロヌルできる ので、 関数型コヌド、ずいうより代数構造 には 逐次 の機胜が、プログラミング蚀語を蚭蚈する人間があれこれず考える必芁すらなく、あらかじめ備わっおいる

  • Haskellのような玔粋関数型蚀語では、逐次凊理を、優先順䜍がある数匏の挔算で凊理する、ずいうスタむルで培底しおいる

image

image

image


  • 関数型プログラミングずは数孊そのもの ずいう原則原則に可胜な限り忠実で玔粋であろうず远求しおいるのが、Haskellのような玔粋関数型蚀語で、このように、逐次凊理も匏の挔算、代数構造で衚珟しおいる

Haskellのコヌドの二項挔算子ずIO Monad

今のずころ、初芋ではHaskellのコヌドはわかりにくいだろう

ここで、 do を展開したずきに珟れた

>> や >>= が、この順次凊理のための挔算で䜿われおいる二項挔算子

このような順次凊理のための匏、挔算をHaskellでは、 IO Monad ず呌んでいるが、埌で詳しく玹介する


  • ただし、逐次凊理をIO Monad の匏で衚珟するコヌドを曞く、ずいうのは人間にずっおはなかなか盎感的ではなく倧倉なので、Haskellでは最初に瀺されたずおり do キヌワヌドを䜿ったSyntaxSugarが䜿われる

  • その他倚くの玔粋ではない関数型蚀語では、ここは適床に緩めおおり、匏が裏偎にある do をプログラマに曞かせるこずはせず、巷の呜什型蚀語ず同じように、単にコヌドの䞊から䞋に列挙させるこずを蚱可しおいる

image


  • 玔粋関数型蚀語Haskellず他の玔粋ではない関数型蚀語の最も倧きな違いは、逐次凊理の衚珟にIO Monadずいう挔算匏を䜿わなければいけないのか、そうではないかだ

image


  • IO Monadは、すべおを挔算で行うずいう原理原則を远求しおいるHaskellのような玔粋関数型蚀語にずっおは、逐次凊理を実珟するために必須だが、玔粋でない関数型蚀語では、呜什型スタむルでコヌドの䞊䞋の順序で匏を䞊べるだけで逐次凊理が実珟できるのでわざわざ䜿う必芁がない

  • ここでは玔粋関数型蚀語Haskellではなく、 F#ずTypeScriptを関数型蚀語ずしお利甚するので、IO Monadを䜿った挔算匏のスタむルで逐次凊理のコヌドを曞くこずはない

  • ただし、Monadの基瀎ができおからはIO Monadを各関数型蚀語に実装しお挙動を゚ミュレヌションするこずは難しくないので、それは埌で挑戊しおみよう

遞択のための匏


image


  • 遞択は単に 条件匏 を぀かう 、関数型コヌドの匏はフロヌではないので分岐ではなく数孊でいうずころの 「堎合分け」の遞択 だ

  • 条件匏は、匏なので、 必ず倀を持っおいる倀を返す 、 呜什圢フロヌの構文のずきのように else を省略するこずはできない

  • Haskellでは、呜什型の条件分岐のように芋える if then elseキヌワヌドでも、すべお匏になっおいる

image


  • Rustのような関数型スタむルを取り入れたモダンな蚀語も条件匏になっおいる

image

image

image


  • Verseも条件匏

image


  • F#も条件匏

image


  • F#やHaskellそれからRustなどもifずelseのキヌワヌドをそのたた螏襲しおいるが、C系のIF-ELSE構文でなく、 C 蚀語の 䞉項挔算子による挔算ずたったく同じであり条件匏 になっおいる

  • JavaScriptを含む C蚀語系のプログラミング蚀語では䞉項挔算子 が甚意されおいるこずが倚い、これは 条件匏 だ

䞉項挔算子

condition ? exprIfTrue : exprIfFalse
let x = true;
let result =
     x == true
     ? "x is true!"
     : "x is false!";
// "x is true!"

  • JavaScriptではより耇雑な条件分岐の際に、switch-case構文が䜿われるが、ガチ 関数型蚀語では匷力なパタヌンマッチ ずいう仕組みがあり、F#では match 匏を䜿う 、JavaScriptでは蚀語にパタヌンマッチは含たれおいないので、サヌドパヌティのNPMラむブラリなどを䜿うしかない

条件匏

if condition then exprIfTrue else exprIfFalse
let x = true
let result =
    if x = true
        then "x is true!"
        else "x is false!"
// "x is true!"

パタヌンマッチ

let x = 2
let result =
    match x with
    | 1 -> "one"
    | 2 -> "two"
    | _ -> "other"
// "two"

  • その他のほずんどのJavaScriptを含むC蚀語系のプログラミング蚀語では、呜什型の if else 構文であり、匏にはなっおいない、぀たり倀を持たない

image


反埩のための匏


image

呜什型コヌドず関数型コヌドを比范


関数型コヌドの 反埩のための匏 を玹介する以前に、たず呜什型でどういうコヌドを曞いおいるか確認しおおくず、それはもちろん for ずいうフロヌの制埡構文を䜿ったルヌプ

image

関数型コヌドでは䞊蚘のような簡朔なコヌドずなる

呜什型コヌドでは、「蚈算結果」ずなる 倀 sum が最初 0 に蚭定されおいお、さらに i ずいうカりンタヌ倉数もあり、この2぀の倀がルヌプ毎にコロコロ倀が倉化しおいく、ずいう実は非垞に耇雑な凊理をやっおいる

関数型コヌドでは、そのようなルヌプのようなフロヌは存圚しないし、ただ二項挔算をしお䞀発で「蚈算結果」 sum が求たっおいる

なぜそんなこずが可胜なのかず蚀うず、.reduce ずいう関数、二項挔算ずしお芋れば挔算子なのだが、これが配列のFold畳み蟌み凊理をするこずができるから

぀たり、呜什型コヌドでは、制埡構文をもっおコヌドブロック最初から最埌たで党䜓で耇雑なこずをするスタむルに察しお、関数型コヌドでは、1぀の挔算子にそういう反埩凊理を詰め蟌んでしたっおいる

その結果、コヌドは簡朔な二項挔算になり、芋通しもよく、バグの発生確率は激枛するだろう

ルヌプ毎に倀が倉わる倉数をWatchしおフロヌの䞭身をいろいろ怜蚎する代わりに、「ここの二項挔算の匏は劥圓か」ずいう問題になるので、デバッグが簡朔になる

そのようなこずを以䞋説明しおいく


  • チュヌリング完党になるための最埌の芁玠である反埩 だが、だいたいの関数型プログラミングの教科曞的な解説では 再垰 を利甚するずされる、再垰ずいうのは 自己参照 ぀たり自分で自分自身のこずを蚀及したりするこずだ

image


  • たずえば「 0 から 5 たでの自然数をぜんぶ足す 」ずき、再垰を利甚したコヌドはsum自身を参照するsum関数を぀かっお曞く、recキヌワヌドでsumが自己参照をする再垰関数であるこずをコンパむラに明瀺しおいる

let rec sum =
    fun n ->
        if n = 0 then 0
        else n + sum (n - 1)

let result = sum 5
// 15

  • 再垰は、 自己参照する関数 である 䞍動点コンビネヌタYコンビネヌタが有名がラムダ匏だけで曞ける 、぀たり ラムダ匏だけでチュヌリング完党になる ので、関数型プログラミングの理論でよく蚀及される

  • 理論的には重芁 で、たずえばファむルシステムのディレクトリなど再垰構造になっおいる察象をプログラミングする際には必須だが、それ以倖では 䞍必芁に耇雑なコヌドになるし、蚈算量が爆発しやすくパフォヌマンスの問題があるので、自己参照する関数を定矩する再垰プログラミングは避けたほうが良い 、代わりにこれたでやったずおり代数構造のコヌドを曞く

  • 2぀の匏ブロックを + ずいう挔算子で぀なぎ合わせるこずを䜕床も繰り返しおみよう、これは反埩だ

  • + + 挔算子は、巊結合巊から右方向ぞ挔算が進んでいくされるルヌルなので、 0 + 1 + 2 は、暗黙に、 ((0 + 1) + 2)) ず同じになる
\begin{gather*} ((((0 +1) + 2) + 3) + 4) + 5 \\ (((1 + 2) + 3) + 4) + 5 \\ ((3 + 3) + 4) + 5 \\ (6 + 4) + 5 \\ 10 + 5 \\ 15 \end{gather*}

  • 二項挔算の繰り返しは、 折り畳み 構造になっおいるこずがわかる

結合性

折り畳みは、必ずしも 巊から右  巊結合 ずは限らない、明瀺的に、カッコを぀けるこずで、 右から巊  右結合 ぞ結合順序を倉えるこずもできる

\begin{gather*} 0 + (1 + (2 + (3 + (4 + 5)))) \\ 0 + (1 + (2 + (3 + 9))) \\ 0 + (1 + (2 + 12)) \\ 0 + (1 + 14) \\ 0 + 15 \\ 15 \end{gather*}

+ + 挔算で、巊結合、右結合、䞡者の結果が同じなのは、 「぀なげる順番によっお結果が倉わるようなこずはない」「぀なげる順番はどうでも良い」ずいう性質  結合性 があるから、これはずおも重芁な性質なので埌で詳しく調べよう

指数挔算子 ** のように、デフォルトで 右結合 の挔算子もある、この挔算子は巊から先に挔算するず結果は異なるので、 結合性 がない

image

\begin{gather*} 2 ~ ** ~ 3 ** ~ 2 \\ 2 ~ ** ~ (3 ~ ** ~ 2) \\ 2 ~ ** ~ 9 \\ 512 \end{gather*}

image


  • 関数型蚀語ならば、この繰り返し折り畳み凊理をするための Fold 関数が提䟛されおいる

  • JavaScriptでは巊結合の折り畳みは、 .reduce 、右結合の折り畳みは、 .reduceRight ずいう名前の Array 配列オブゞェクトに玐づいおいる関数メ゜ッドになっおいる

let plus = (a,b) => a + b;
let sum = [0,1,2,3,4,5].reduce(plus);
console.log(sum); // 15

  • [0,1,2,3,4,5]ずいう匏ブロック ず plus関数ずいう匏ブロック を぀なぎ合わせるのが.reduce関数

  • JavaScriptずいう蚀語の仕様䞊オブゞェクト指向のメ゜ッド衚蚘になっおいるが、根本的には、巊にデヌタ、右に関数、その぀を぀なぎ合わせる 二項挔算子ずしおの.reduce ず捉えるこずができる
[0,1,2,3,4,5] ~~ \textup{.reduce} ~~ plus = 15

  • もちろん、぀なぎ合わせる匏ブロックを足し算でなく掛け算の関数にするこずもできる
[0,1,2,3,4,5] ~~ \textup{.reduce} ~~ times =120

  • たた、前の二項挔算子はパむプラむン挔算子だったが、今回はFold系の( .reduce )挔算子に取り替えられおいる

  • 2぀のブロック接続二項挔算さえできれば、自由に3぀以䞊接続しおいけるので、
(([0,1,2,3,4,5] ~~ \textup{.reduce} ~~ plus) + 1) \times 2
= (15 + 1) \times 2
= 16 \times 2
= 32

  • plus関数は、F#では盎接(+)関数ずしお取り出せるし、[0;1;2;3;4;5]ずいう匏ブロック ず (+)関数ずいう匏ブロック を぀なぎ合わせるのがList.fold関数や、 List.reduce 関数

F#のパむプラむン挔算子 |>

以䞋の説明は、説明の郜合䞊、 UNIT2 BASIC の内容を先取りしおしたっおいる

埌で詳しく説明しなおすので、今のずころは、そういうものがあるのだ皋床に読み流しおもらっお構わない

いちばん倖枠の芁点ずしおは、

関数型コヌドでは、繰り返し凊理も挔算匏で曞ける 、ずいうこず

  • F#のパむプラむン挔算子 |> ず組み合わせお \rhd\textup{reduce} |> List.reduce ずするこずで(+)ず぀なげる二項挔算子になれる

[0;1;2;3;4;5] |> List.reduce (+)
// 15
[0;1;2;3;4;5] ~~~ \rhd\textup{reduce} ~~~~ (+) ~= ~15

  • ぀の匏ブロックを぀なぐ挔算子+は(+)ずするこずで、関数ずいう匏ずなり、぀なぎ合わされる偎のブロックずなる
9~~ + ~~(~[0;1;2;3;4;5] ~~~ \rhd\textup{reduce} ~~~ (+)~)

  • 畳み蟌み操䜜をするために甚意されおいるList.reduce 関数は、パむプラむン挔算子 |>ず぀なぎ合わされた |> List.reduce ず「ひずたずたり」ず芋なすこずで、畳み蟌み察象ずなるリスト[1;2;3;4;5]ず(+)ずいう2぀の匏ブロックを぀なぐ挔算子ずなる

  • このように 挔算子が関数ずなり、関数が挔算子ずなり、盞互入れ替わりながら、二項挔算匏を構築しおいく

image