自分の頭の中のプログラミングパラダイムは、次の2つで占められています。
- 手続き型: データ(構造体)と処理(関数)は明確に分離される。
- オブジェクト指向(OOP): データ処理をクラスという単位でカプセル化(統合)し、一体モノとして扱う。
OOPで一体モノとして扱っていますが、頭の中では別のモノとして認識しています。
ところが、最近、PowerQuery(M言語)を通じて「ラムダ計算」の世界に触れ、そこにはさらなる抽象化の段階があり、
- 「データも処理も区別なく「すべて『関数』」として扱う」 というパラダイムでした。
PowerQuery(M言語)で True や False といった真偽値そのものを関数で表現したり、NOT関数を実装していくなかで、この「すべてが関数である世界」の特殊さと、そこで見つけた 「純粋なロジックと具体的な値(バナナとリンゴ)の関係性」 が、手続き型やOOPに慣れ親しんだ頭にカツンと来たので覚書しています。
1. 衝撃、データすらも「関数」である
ラムダ計算(チャーチ・エンコーディング)のアプローチでは、 True や False といった真偽値すらも、静的なデータではないということ。衝撃でした。チャーチ・エンコーディングでは、これらは「2つの選択肢からどちらかを選ぶ」という機能を持った関数として定義します。
// MyTrue: 2つの引数を受け取り、1つ目(x)を返す関数
MyTrue = (x, y) => x,
// MyFalse: 2つの引数を受け取り、2つ目(y)を返す関数
MyFalse = (x, y) => y,
手続き型やOOP脳の私の頭は 「フラグ(データ)を見て分岐する」 という考えをしますが、ラムダ計算では、 「フラグ自体が分岐させる機能を持っている」 イメージです。
さらに、論理否定 NOT 関数を作ったのですが、これはかなり難解でして、自力では作れず、生成AIを頼りに作りました。
出来上がったコードを後から見て言語化してみると、 「True(1つ目の引数を返す人)」が来たら、「False(2つ目の引数を返す人)」に変身させる イメージなのかなぁといった感じです。
// MyNot: 真偽値関数(bool_func)を受け取り、その適用順序を入れ替える
MyNot = (bool_func) => (x, y) => bool_func(y, x),
ここで MyNot(MyTrue) を実行しても、まだ具体的な値(文字列や数値)は一切登場しないのです。返ってくるのは MyFalse と等価な 「関数(振る舞い)」 だけです。
めちゃくちゃ抽象の世界です。
2. 「論理の構築」と「値の適用」
ここまでできて「面白い」と思ったのは、 「論理構造の構築」と「具体的な値の適用」 が完全にフェーズ分けされているという点です。
以下のPowerQuery(M言語)のコードは、 NOT 関数をテストしているコードですが、前半部分の論理構築フェーズでは、純粋に関数だけを操作し、後半でやっと現実世界の値を持ち込んでいます。
let
// ==========================================
// 1. 論理構築フェーズ
// ここには具体的なデータは存在しない。純粋な関数の組み合わせのみ。
// ==========================================
// MyTrue: 2つの引数を受け取り、1つ目(x)を返す関数
MyTrue = (x, y) => x,
// MyFalse: 2つの引数を受け取り、2つ目(y)を返す関数
MyFalse = (x, y) => y,
// MyNot: 真偽値関数(bool_func)を受け取り、その適用順序を入れ替える
MyNot = (bool_func) => (x, y) => bool_func(y, x),
// 「反転させる」というロジックを定義
// Logic_Inverted は、「2つの選択肢が来たら後ろを選ぶ」という性質を持つ関数となる
Logic_Inverted = MyNot(MyTrue),
// ==========================================
// 2. 値の適用(注入)フェーズ
// 構築された抽象的な構造に、具体的な値を適用(流し込んで)結果を得る。
// ==========================================
// パターンA: 文字列という「具体的な世界」を適用(注入)する
// -> 仕組みに従い "リンゴ" が選ばれる
Result_String = Logic_Inverted("バナナ", "リンゴ"),
// パターンB: 数値という「具体的な世界」を適用(注入)する
// -> 仕組みに従い 0 が選ばれる
Result_Number = Logic_Inverted(100, 0),
// 結果をリストに変換
Result ={ Result_String, Result_Number }
in
Result
こんな結果が返ります。Trueの場合は2つの引数の1つ目が返り、Falseは2つ目の引数が返るので、想定通りの挙動となっています。

3. なぜ最後にバナナとリンゴを渡すのか?
NOT 関数の作成を通して、自身の最大の気づきは 「関数(ロジック)」と「値(コンテキスト)」の完全な分離 です。
- Logic_Inverted(関数): ここにあるのは「2つの引数の内、後ろの人を選ぶ」という純粋な構造だけです。型もなければ値もないです。
- ("バナナ", "リンゴ") (値): ここで初めて、その構造に対して、人間が解釈可能な実体を与えます。
OOPではインターフェースを使ってポリモーフィズムを実現しますが、ラムダ計算では全てが関数で表現され、とにかく抽象度が高いので、インターフェースのような特別な仕掛け無しに 「文字列でも数値でも、構造さえ合えば何でも受け入れる」 という柔軟性を発揮する点が衝撃的でした。
「関数」は純粋なルールの世界に住んでいて、「値」はそれを現実世界に具現化するために「最後」に渡される
そんな感覚でした。