関数型プログラミングのセカイ

はじめに

次世代デジタル基盤開発事業部のIと申します。 筆者は、メインの仕事として、Webサイトの設計・実装を行っております。 また、個人的にソースコードの品質を担保する方法を試行錯誤しており、プロジェクトに還元し、そのためのドキュメントをプロジェクトメンバーや部全体に共有しています。

今回は、ソースコードの品質担保に大きく寄与するプログラミングパラダイム、関数型プログラミングについて筆者なりの考えなどを共有して、理解の手助けにできればと考えています。

プログラミングパラダイム=プログラミングの「セカイ」

前節で、関数型プログラミングを「プログラミングパラダイム」と書きましたが、実質その通りで、プログラムを組むとき、どのように物事(データや処理)を捉えて(詳細)設計や実装を(以降、これを一言で「実装」とまとめます)するかどうかの話です。

(以降では、「プログラミングパラダイム」を略してPPと呼ぶようにします)

とはいえ、「某PPでは、〇〇を〇〇と捉えている」と書いても客観的過ぎて腑に落ちる感覚がつかめないのも事実です。 しかも、PPの全体像を論理的に説明しようとしても、全体像が広すぎて収集が付かなくなるリスクがあります。 特に、今回説明する関数型プログラミングは、数学や哲学の沼が待ち構えていますので、もっと平易に理解できる観点を持つ必要が出てきます。

そこで、本記事では、PPを、アニメやラノベで使われる「世界線」の観点で考えるようにします。つまり、「それぞれのPPはそういう世界なんだ」という観念で進めていって、そのうえで論理的な理解を進められるような役割で説明を進めていきます。

突然ですが、筆者は「プロジェクトセカイ カラフルステージ! feat. 初音ミク」というリズムアクションゲームが大好きで、毎日タブレットとにらめっこしております。 このゲームにはリズムアクションゲームながらストーリーがあり、現実世界(シブヤ)と、登場人物や所属ユニットメンバーの「本当の想い(メンバーたちが心の底からやりたいこと、実現したいこと)」で作られた「セカイ」を通じて彼らの進む道を目指していきます。*1

この「セカイ」の概念が、「先人たちが『この処理を忌憚なく実現しよう』『バグのないコードを書けるようにしよう』という想いが募って現実となった」PPの説明と相性が良いので、これからはPPのことを「プログラミングのセカイ(以降、『セカイ』)」と呼ぶようにします。

大きなセカイと小さなセカイ

前節では、PPを「セカイ」に例えると説明いたしましたが、プロセカのそれとは大きく違うものがあります。各セカイの構造です。

プロセカのセカイはそれぞれフラットに存在していますが、プログラミングでのセカイは2階層(上下階層)構造になっています。

まず、上層には「大きなセカイ」が3つあり、これらは「データの抽象度」で区別されます。その違いを、以下の図で簡単に列挙します。

大まかなプログラミングのセカイ

続いて下層には、多数の「小さなセカイ」が存在しています。また、すべての小さなセカイは、必ず大きなセカイのどれかから紐づいています。 プログラミング言語やその処理系は、小さなセカイから一つ、もしくは複数を選び、それ(ら)に則ることで実現すると考えます。

小さなセカイの中で代表的なものをピックアップして、一覧形式(下図参照)で列挙します。

小さなセカイとその代表例

このように、セカイは概念上のものですが、古の言語から最近よく使われるフレームワークまで、言語に囚われない形で実現しています。

では、小さなセカイの表で「後述」としている、関数型のセカイを解説していきたいのですが、系統立てて、関数型のルーツとなる「宣言型のセカイ」を軽く紹介していきます。

宣言型のセカイ

宣言型プログラミングでは、文字通り「宣言する」ことでプログラムを組んでいきます。「宣言する」ということについてですが、何を宣言するのでしょうか? 変数を宣言するのでしょうか?

答えは、「論理」です。数学の証明でよく見かけた、「〇〇は××である」です。 宣言型プログラミングでは、数学で証明を記述するかのように、もっと言うと、数学的に正しくプログラムを組んでいくのです。 *2

(ここからは、宣言型のセカイによく馴染められるように、本記事では、命令型でよく使われる「実装」「実行」という言葉を使わず、「宣言」「評価」という言葉を使っていきます)

例を挙げて、違いを分かりやすく解説します。

例えば、命令型では、「変数 X に 100 を代入する」「12 を引数として関数 f を呼び出し、その結果を変数 Y に代入する」「文字列変数 S を表示する」と、コンピューターに命令するように考えて組んでいきます。

これを宣言型に置き換えると、「変数 X と 100 は同じである」「変数 Y と、12 を引数とした関数 f の評価結果は同じである」と考えるのです。

さらに、数学的にあり得ない「文字列変数 S を表示する」という概念は存在しないと考えます(別の節で、宣言型のセカイでの対応方法を説明しておりますのでご安心を)。

命令型のセカイでは、データは具体的に「データ」という認識ですが、宣言型では「何かはわからないけどなんらかの値」「なんらかの値の集合」という、数学らしいふわっとしたもの(数学ではモデルと呼ばれているものです)という認識です。 *3

先ほど例に出した「変数 Y と、12 を引数とした関数 f の評価結果は同じである」ですが、宣言型では、「関数 f の評価結果」はずばり「関数 f の評価結果」であって、「戻り値」という概念も考えないのです(こちらも、別の節で説明します)。

ただそれだけでは、いつまで経っても評価が始まらないので、たいていの言語では main 関数やイベントドリブンを組み入れたりして評価を始めるきっかけを用意しています。なので、どこから宣言するのか迷うことはありません。

ところで、先の説明では「論理」を宣言していくと書いていましたが、論理自体をどのようにプログラムにしていくのでしょうか。これが、小さなセカイへの分岐点となっていくポイントなのです。そして、関数型のセカイでは、宣言を関数で組んでいくセカイなのです。

関数型のセカイ

前節でも述べたように、このセカイの大きな特徴として、宣言の単位を関数としている点です。 このセカイでは、関数を定義・呼び出すことで論理を宣言していき、プログラムとして組み上げます。 *4

しかし、のべつ幕なしに関数を定義して呼び出すわけにはいきません。数学的な正しさを要求されるからです。そこで、関数型のセカイでは、プログラムを組む際に重要な3つの条件を設けています。これらは、それぞれが違う観点から設けられています。

  1. 関数定義的観点からの条件:参照透明を遵守する
  2. 処理系的観点からの条件:評価したい時に評価できる(遅延評価)
  3. 言語仕様的観点からの条件:関数がファーストクラスオブジェクトである

では、1番目の「参照透明」から条件を解説していきます。

関数定義的観点から・参照透明

それでは、参照透明はどういうものなのかを、3つの特性でまとめてみます。

  • 実引数が同じならば結果は必ず同じ
  • 実引数や関数内で生成した集合は、関数が終わるまで変化しない(=イミュータブル)
  • 関数外の要素が結果に依存しない(=副作用が無い)

理解を深めるためのサンプルとして、参照透明なプログラムを組んでみます。 特定の言語で書いたときの言語思想につられてしまうことを懸念して pseudo code で組んでみます。

# 参照透明な関数の例
def referentialTransparent(x, y) {
  if (y < 0) {
    return x + y
  }

  return x - y
}

この関数では、引数 x, y の組み合わせが同じならば必ず同じ値を返しますし、関数の途中で x, y の値が変わることがありません。呼び元の値も変わりませんし、新しい集合(ここでは変数)を変化させません。ましてや、評価中は関数外の集合に影響されませんし、影響させません。

ここでは単純なプログラムのため、他の要因は削るようにしたのですが、本格的に対応しようとすると、命令型に慣れている人にとってかなり戸惑うことになると思います。というのも、例外発生・画面への出力・ファイル入出力・データベース操作が副作用になるセカイだからです。*5

とはいえ、こうなるのはごもっともな話です。数学では関数を評価しても例外をスローしませんし、画面に何も出しません。データベースに何も訊きません。

数学的に見ればごもっともですが、コンピューターを扱うならば言語道断な話です。関数型プログラミングではどのように対応しているのでしょうか。

ちょっとだけ裏側を垣間見てみましょう。

宣言型と命令型のセカイの狭間

実は、宣言型を実装できる処理系やフレームワークには、「宣言型のセカイ」と「命令型のセカイ」との橋渡しを用意しています。こうすることで、それぞれのセカイを奇麗に区分けし、それぞれのセカイで忌憚なくプログラミングできるようになります。 *6

具体的には、以下のようにします。

  • 評価結果を処理系や命令型で実装したセカイに丸投げ
  • 副作用を含める型を作る

処理系に丸投げしている代表例としては、react.jsやVue.jsなどのリアクティブJavascriptフレームワークやライブラリです。 例えば、Vue.jsのテンプレートやイベントハンドラは命令型で実装することになりますが、必要な値を導き出す関数やcomputed関数を使うは宣言型になります(もっとも、Javascriptなので宣言型を無視できてしまいますが…)。 更に、Vuexなどのストアライブラリを併用することによって、参照透明を妨げる状態変数の存在を抽象化できるようになります。

副作用を含める型といえば、やはりHaskell由来の「モナド」でしょう。評価結果に「処理系でこの結果を表示する」ことも添えることで、参照透明を壊すことなく画面に結果を表示できますし、ファイルの入出力やデータベースの操作ができるようになります。これらの型が無かったとしても、モナドの思想を反映した型を作ることで参照透明を維持することも考えられます。

突き詰めようとすると更に膨大な記事になってしまうので、ここでは「こういうものだ」という認識で進めていきます。

宣言を並べるということは、時が流れているということ

ここで次の条件を説明する前に、補足説明をしたいと思います。

命令型のセカイでは、複数行のコードは「上から下に手順が記されている」と考えています。時間軸は全く考慮しません。 では、関数型のセカイではどう考えるのでしょうか。

百聞は一見にしかず、試しにコードを組んでみます。

# この関数の評価結果は「aとbを足したもの」となる
def func(a, b) {
  return a + b
}

# [各行の解説]
# (1): z1 は、 x を 10 倍したものである
# (2): z2 は、z1 と y を引数に取った関数 func を呼び出した結果である(z1 と y を足した結果と考えない!)
# (3): この関数の評価結果は 「x を10 倍したものと y を引数を取った関数 func を呼び出した結果を 2 倍したもの」となる
def sampleFunction (x, y) {
  z1 = x * 10           # ... (1)
  z2 = func(z1, y)    # ... (2)

  return z1 * 2        # ... (3)
}

ここで、関数 sampleFunction は (1)~(3) の宣言(理論)で成り立っていますが、(1)~(3)は同一時間に存在しないということに注目します。今回は、(1)→(2)→(3) の順に時が流れていると考えます。 *7

単純に並べるだけでは関連性を証明できなかった (1) と (2) の z1 でしたが、これで同じものだと認識できるようになります。

また、宣言単体には時の流れが存在しません。言葉を変えると「それぞれの宣言はスナップショットのようなもの」でしょうか。つまり、(1)と(2)は命令型のセカイでは代入ですが、関数型のセカイでは等式として認識されます。

こういうことからも、関数型のセカイでは、右辺と左辺で内容が変わってしまう再代入はありえないものという認識になります。 *8

宣言のスムーズな流れをなるべく妨げない心得

宣言を連ねていくとき、命令型のセカイではよくやる分岐や反復はどう認識すればよいのでしょうか?

まず分岐ですが、ここはあまり気にしなくてよいと思います。 数学の証明や定義でも、「ただし〇〇のときは××」というように理論を切り分ける場面はたくさんあります。 なので、参照透明を遵守する範囲内なら使っても差し支えありません。

しかし、反復となると厄介です。ほぼ間違いなく状態変数を使いますし、数学的にも好ましくありません。 この点に関しては先人も認識済みで、状態変数を使わない反復を実現するための手助けを備えています。 集合の要素を引数とする関数を渡して、新しい集合を返す関数です。 Javascript/Typescript や Ruby 、Python などの言語やライブラリで用意されている、 map や reduce 、find や filter といった関数(メソッド)がそれに相当します。 反復を処理系に丸投げすることで、宣言型のセカイで反復を実現できるようになっています(参照透明はマストですが)。

ここまで参照透明やその背景を見てきましたが、実際問題、いつ、どうやって評価するのでしょうか。そこで、実際の関数評価ー処理系に注目して説明します。

処理系的観点から・遅延評価

これまでの説明で、命令型に慣れていらっしゃる方々は「命令はすぐに実行されて結果が返る」という考えに慣れきってしまっているために困惑するかと思います。 命令型のセカイでは、「要素数が無限の配列」なんて絵空事ですし、実行しようものならすぐにメモリ不足になります。 しかし、数学の世界では、あらゆるものが抽象的になっているわけですから、「無限」さえも扱えてしまいますし、宣言できてしまいます。

とはいえ、関数型のセカイのプログラムといえども、最終的にコンピューター上で実行できる(≒命令型のセカイで実行する)状態にしないといけません。

そこで、関数型のセカイでプログラムを組む時は、評価のタイミングをずらせることが条件となります。つまり、関数型のセカイの処理系では「評価したい時に評価する(このことを遅延評価すると言います)」できる土台が準備されているのです。

遅延評価できるセカイでは、関数を評価した結果はどう考えるのでしょうか。それは、そのものずばり「関数を評価した結果」と考えます。値は何かとは考えないのです。

例えば、「1を初期値とした、差1の等差数列の5番目から10番目までを返す」という評価を関数で宣言してみようとします。 これを実際のプログラムで書くと混乱を招くので pseudo code で書いてみます。

# range(init, step) ⇒ 初期値initからstepを加えていく、無限の数列
# take: rangeの評価結果のうち、from番目からto番目までの範囲を抽出
take(range(1,1), 5, 10)

パッと見、命令型のセカイではありえない書き方です。 range 関数の結果が無限の数列なのですから。無限ループになると認識して思考停止します。 しかし、関数型のセカイでは理に適った内容で、この場合、評価結果は「1,2,3...の数列のうち、5番目から10番目までの結果」です。かなりふわっとしていますね。

では、実際に値が必要な時はどうするのでしょうか。答えは、命令型のセカイで値を取得する関数を呼ぶです。

例えば、評価結果から配列を取得する関数として toArray があったとします。

# [] ⇒ 集合の初期値。ここでは空の配列
#         つまり、ここで集合が具体的に何かを指定している
result = toArray(take(1.., 5, 10)) # ⇒ [5,6,7,8,9,10]

toArray 関数の中では以下のように評価されます。

1. 空の配列を用意する
2. range関数を実行していく
3. 5回目から結果を配列に追加する
4. 10回目まで追加したら終了

こうすることで、toArray関数を呼び出した時にtake関数を評価して値を受け取れるようになります。つまり、take関数を評価するタイミングをずらしたー遅延評価したというわけです。 これで、関数型の特性を壊すことなく、結果を受け取れるようになります。

ちなみに、先ほど説明した「モナド」や「処理系丸投げ」も、遅延評価とタッグを組むことで大きな効果を発揮します。

となると、関数を引数として渡せる言語仕様になっているかどうかがカギになります。そこで、観点を言語仕様に向けてみます。

言語仕様的観点から・関数がファーストオブジェクト

先ほど説明した遅延評価は非常に重要なのですが、それを実現するためには条件があります。それは、言語仕様で関数がファーストクラスオブジェクトと定義されていることです。

ファーストクラスオブジェクトの詳説については、Wikipediaの記事をご参照いただければということで、本記事向けに4つにまとめてみました。

  • 無名関数を作れる
  • 引数として関数を取れる
  • 評価結果として関数を返せる
  • 実行時に、関数を評価結果として作れる

仕様レベルで上記特徴がすべて当てはまる言語として、アロー関数が使えるJavascript や Typescript 、ラムダ式が使える Python や C# などがあります。

おそらくここでピンとくる方がいらっしゃると思いますが、関数の評価結果が「その関数を呼び出す関数」ならば、評価したい時に評価できる、つまり遅延評価が実現可能ということになります。先の takeRange 関数を例に手を加えてみます。 更に、実装がわかりやすくなるように静的型付けも付けておきます。

### 遅延評価用モジュール
### 命令型のセカイ
###
### Typescriptから以下の書式を借用
### ・型付け
### ・アロー関数
### ・number型
### ・type式
### ・ジェネリック
###
### Pythonから以下の書式を借用
### ・yield
###
### collectionTypeは抽象的な集合型
### append<T>(collection: collectionType<T>, value: T)は、collectionの要素として引数valueを追加する関数

type sequenceFunction = () => number
type collectionGenerator = <T>(init: collectionType<T>) => collectionType<T>
type numCollection = collectionType<number>

# 数列のように値を返す関数を生成する関数
def sequenceFactory(init: number, step: number): sequenceFunction {
  return () => {
    value = init # 関数型のセカイで参照透明を保持するため、複製する

    while(true) { # 無限ループ
      yield value

      value = value + step
    }
  }
}

def take(sequence: sequenceFunction, from: number, to: number) => collectionGenerator<number> {
  return (initialCollection: numCollection): numCollection => {
    collection = copy(init) # 関数型のセカイで参照透明を保持するため、複製する

    count = 1

    while count <= to {
      if ( count < from ) {
        sequence()

        continue
      }

      append(collection, sequence()))

      count = count + 1
    }

    return collection # 数列の from 番目から to 番目まで
  }
}

def toArray(logic)
  return logic([])
end
### ロジック用モジュール
### 関数型のセカイ

# 1,2,3,... の数列の5番目から10番目までの評価結果を受け取る
ret = take(sequenceFactory(1, 1), 5, 10)
### 表示処理モジュール
### 命令型のセカイ

print(toArray(ret)) # ⇒ 画面上に "[5,6,7,8,9,10]" と表示される

関数を生成して、それを返すことによって関数を評価するタイミングをずらすことができるようになりました。 また、モジュール別にすることで、セカイのコンテキストを切り分けることが可能になっています。 こうすれば、画面に出力するモナドも、「画面に出力する値を返す関数を持っている集合」と捉えれば腑に落ちるのではと思っております。

上記のコードでピンと来るかもしれませんが、言語仕様として静的型付けがあると、要求外の関数を扱うリスクを抑えられますのでプログラムを組む際は大変助かります。

関数型のセカイのメリット

ここまで書いたところで、ようやくメリットを説明できるようになりました。 先人たちは、これらのメリットを生かせたいといった想いが関数型のセカイを生み出したと言っても過言ではありません。 とはいえ、詳細に説明するとさらにボリューミーになりますので、簡単に列挙するに留めます。

後述の参考文献で紹介している「関数型プログラミングの基礎」では、メリットをページを割いて紹介しております。

小回りが利くモジュールを作れる

数学的な関数として考えていると、内容を細かい単位で分割して、少ないコードで定義するようになります。 部品を組み立てるように複雑な処理を実装できるようになります。

依存の少ないコードになる

あらゆる要求をこなす(特にアジャイルで開発している場合)ためには、依存性の少ないコードが大事です。 関数型のセカイでは、物事を抽象的に捉えるプログラムを組むことになるので、自然と依存性が少なくなります。

処理のパフォーマンスを上げられる

参照透明な関数を使うときは、非常に複雑で時間がかかる関数だとしても、引数が同じならば同じ結果ですし、副作用を考慮する必要はありません。 なので、「一旦結果がわかれば、同じ引数の時は評価を端折れる」と考えるのは道理です。 例えば、引数と結果でハッシュを作っておけば、大幅な処理時間の削減に寄与できます。

テストがしやすくなる

参照透明な関数は、テストをするときに結果が予測しやすくなります。 つまり、ユニットテストを実装するときにアサーションを作りやすくなくなります。 更に、関数化するということはモックやスタブにしやすくなりますので、副作用を思い通りの内容に変えることもできます。

関数型のセカイのデメリット

一番大きなデメリットはプログラムの構成が膨大になることです。 パフォーマンスに関わる部分では慎重なトレードオフの判断が必要です。

また、かなり長い文章になっていることからもわかる通り、業務として対応する場合は、ある程度の学習コストが必要になります。

おわりに

ここまで、関数型プログラミングについてつらつらと書いてまいりましたが、膨大な文章量になってしまったことをお詫び申し上げます。

とはいえども、どのような方が読者になられるのかを絞り切れなかったため、仮として「命令型プログラミングを嗜んでいる方」「いろいろな言語やフレームワークを触っていたら、気が付いたら関数型ライクになっていた方」を対象読者としていましたが、それでも、リアルデータ認識からの脱却から始めないと掴みきれないのは事実です。 *9

そして、コンピューターでプログラムを組むことにとって、最大の摂理は「コンピューターは与えられた命令を、即座に、従順かつ順番に実行していく存在しでしかない」ということです。これこそ、何物にも妨げられない事実なのです。

しかし、プログラムを組むのは人間です(AIは置いといて)。人間が求めるプログラム像を思い通りの形で実現したいという想いが集まって色々なセカイができ、「宣言型のセカイ」が生まれ、「関数型のセカイ」が生まれました。

一瞬、難解そうなそれぞれのセカイですが、背景や捉え方を学ぶことでより納得し、実作業に落とし込み易くなるかと存じます。本記事がその研鑽の一助になればと祈念して筆を置きたいと思います。

長らくのご精読お疲れさまでした。

おまけ1・void 型の取り扱い

C や Javascript などでは、「値を返さない型」という、数学的に矛盾したことを明示するために void 型というものを用意しています。しかし、先述の通り関数型のセカイでは void は存在してはいけないものです。 というのも、関数ですから、必ず評価結果を返さなければならないためです。そのため、関数型のセカイでは、必ず何かしらの結果を返す必要があります(引数そのものを返してしまうのが通例です)。

ちなみに、UEFN(Unreal Engine for Fortnite) で使われる言語 Verse は関数型のセカイに属していますが、実は void 型が存在しています。ただ、この型が使えるのは戻り値としてだけで、どんな引数でも false を返すようになっています(参照透明を守れるのでアリです)。

おまけ2・独断と偏見で選ぶ、関数型のセカイを垣間見やすい言語

関数型のセカイは、これまでの説明通り、厳密な数学的実装を守るといった話ではないので、実装する言語が関数型に特化する必要はありません。さらに、いきなり純粋関数型言語を導入しようとすると、学習・導入コストが余計跳ね上がります。そこで、Javascript をある程度理解していればコストが小さい Typescript をお勧めします。

Javascript に静的型付けができるようにしたものですから、node.js を用意できればプログラムを組むハードルは低いですし、先人の知恵もいろいろあります( lodash や just 、 immer 等)。

また、 Typescript を使うとモナドを実現しやすくなります。例えば、 Maybe モナドは以下のように定義できます。

type maybeNumMonad = number | null

参考文献

ラノベ風に関数型プログラミングの考え方を説く本です。

命令型プログラミングで凝り固まった概念を、アラン・ケイのみならず、プラトンやデカルト、スピノザまで用いて、これでもかというぐらい哲学的思想をもってひっくり返そうとしている姿勢はまさに力作と思います。

ただ、本書の刊行が2015年で、Node.js と io.js が統合されておらず、Typescript や React 、 Vue がまだ出て間もない時代の本なので、今と時代があっていない話もあったり、実装のノウハウを得たいという人にとってはこの本は力不足なので、後述の「関数型プログラミングの基礎」や「なっとく!関数型プログラミング」を併読するのをお勧めします。

「関数型プログラミングに目覚めた!」が概念を捉えるための本ならば、本書は実装の基礎を築くための本です(余談ですが、本書でもデカルトが出てきます)。

関数型プログラミングを実践することで得られるメリットをページを割いてじっくり説明しており、そのメリットを享受できる方法や考え方を Javascript を使って教示しているため、実装ノウハウを根本から鍛えられます。また、副作用を参照透過性を持ったまま扱える「モナド」の考え方や作り方も教示しています。

ただ、筆者の個人的な話ですが、本書では、Javascript で関数型プログラミングをする難点として、「引数として渡すときの型付けが無いために関数を渡す際にリスクがある」と書かれていますが、今は Typescript を使えば、この弱点は克服できると考えています。

Java VM で動く関数型プログラミング言語・ Scala で仕事している著者が、関数型プログラミングの考え方や実装方法のみならず、関数型の範疇で例外処理や I/O をモナド的思考で実装する方法を段階的に提供するといった、痒い所に手が届く本です

テコテックの採用活動について

テコテックでは新卒採用、中途採用共に積極的に募集をしています。 採用サイトにて会社の雰囲気や福利厚生、募集内容をご確認いただけます。 ご興味を持っていただけましたら是非ご覧ください。 tecotec.co.jp

*1:ちなみに、彼らの想いを突き進めていくために背中を押していく存在して、初音ミクや巡音ルカなどの「バーチャルシンガー」が登場します

*2:いろいろな文献で、よく「ロジック」という言葉が使われれていますが、宣言型での視点だとしっくり来ますね

*3:本当は圏論も用いるべきなのですが、そうなると、筆者も理解できないセカイであり、話が取り留めなくなるリスクがあるので、ふわっとした説明にとどめます

*4:純粋に数学の考えを守ろうとすると、引数は必ず1つという条件があり、そのための宣言手法があるのですが、純粋に守ると混乱する恐れがあるため、本記事では引数が複数あっても大丈夫なセカイとしています

*5:さらに言うと、Javascriptでの new Date() も副作用があるものと考えます

*6:プロセカで例えると、登場人物が持っているスマートフォンがそれに相当します。このスマホでシブヤと各セカイを行き来するという設定です

*7:数学の証明では時の流れは関係ないですが、関数型のセカイといえどコンピューター上でプログラムが動くのは摂理なので、こういうものだと捉えたほうがスムーズに理解が進みます

*8:もっとも、再代入が避けられないときは、関数を作ってその結果を受け取るという逃げ道はあります

*9:そのためか、筆者が読む関数型プログラミング解説書では哲学の話がよく出てきます