技術memo

関数型ゴースト

プログラミングパラダイムの話への補足、あるいは「正しさ」について

先日の記事 関数型・オブジェクト指向なプログラミングパラダイムについて思うところ - 技術memo で誤魔化したいくつかのことについて、補足します。

オブジェクト指向プログラミング言語の種類、用語

オブジェクト指向プログラミング言語では、Smalltalkを代表とするようなメッセージパッシングを主とする言語と、C++Javaといった言語のオブジェクト指向とでは、毛色が違うと言われています。先日の記事では用語が少々混乱していたので、詳しいところは他の方の記事を参照されるといいでしょう。*1

静的型検査のこと

先日の記事では、オブジェクト指向プログラミングにしても、関数型プログラミングにしても、静的な(コンパイル時の)型検査については棚上げしていましたが、その有無はコーディングスタイルに大きな影響を与えます。*2

「静的型検査がある言語では、型をいちいち記述しなければならないため記述量が無駄に増えてしまい、開発効率が悪い」という言説は、今では昔の話です。型推論が強力な言語、例えばOCamlやF#の例を見ましょう。

個人的には、静的型検査がある関数型プログラミング言語は「型が合えば実質ほぼ完成」というくらいの安全さで書けるので好みです。*3 しかし、特に抽象的なプログラミングをする場面においては、型検査の能力自体がボトルネックとなることもあるようです。*4

状態のこと

プログラミングパラダイムにまつわる議論では「状態の管理」がたびたび争点となります。例えばこの辺り: 「オブジェクト指向プログラミング」と「関数型プログラミング」のたった一つのシンプルな違い - Qiita

「状態をオブジェクトの内部に閉じ込めて、書き手が明示的に管理を行うのは、ある程度複雑なシステムでは困難になる」といったようなことがよく言われます。実際ドメイン駆動設計Effective Javaでも、可能な限りイミュータブル(不変)なデータ(値オブジェクト)を用いた設計が勧められます。

正しく認識すべきことは、「関数型プログラミングだからイミュータブル(不変)」「オブジェクト指向プログラミングだからミュータブル(可変)」という対応は必ずしも成り立たない、ということです。関数型プログラミング言語でも可変な状態を扱う手段は存在し*5、また上記のように、オブジェクト指向プログラミングでもイミュータブルな設計は重視されます。

リアクティブプログラミング(FRP: Functional Reactive Programming)は、関数型プログラミング的な発想から生まれたと言われる、状態管理に有用な手法です。*6 C#のReactive Extensionsというライブラリから大きく普及するようになったこともあり、今では多くの「オブジェクト指向プログラミング言語」(あるいはオブジェクト指向ベースのマルチパラダイム言語)にも移植があります。*7

プログラミング言語利用者としては、原理主義にならずに、有用な道具を学び、使っていくのがよいと思います。*8

参照透過性、あるいは副作用のこと、また遅延評価のこと

関数の性質として、「参照透過性がある/ない」「副作用がない/ある」「純粋/不純」と言われることがあります。「同じ引数で関数を呼び出したら、必ず同じ結果になる」「返り値以外に何らかの効果を及ぼさない」といった性質がある場合に、「参照透過性がある」とか「副作用がない」「純粋」などと言われます。 *9

例えば、返り値が無く(void)、コンソールに引数を出力するような所謂print関数は、副作用がある関数の代表でしょう。 しかしそもそも、関数とは何か、副作用とは何かといったそれ自体が、厳密には微妙な問題を含んでいます。*10

参照透過性とセットで語られがちなのが、「遅延評価」というものです。これも実際には何がそう呼べるかは微妙な問題がありますが、言語全体で原則「関数呼び出しについて、必要になるまで引数を評価しない」ような言語なら、遅延評価と呼んで間違いは無いと思います。 *11

遅延評価が言語機能として組み込まれていると、プログラムが「宣言的*12」に書きやすくなったり、副作用を明示する必要があるためにより良い設計でコーディングしやすくなる、といったメリットがあるようです。*13

しかしながら、低レベルな記述との相性や、その扱いの難しさ(遅延評価⇒モナド・副作用明示が前提なこと、あるいはパフォーマンスへの最適化など、現実的な問題)から、その導入は一部の言語に限られているようです。*14

正しいものは(なかなか)普及しない、あるいは実用について

正しさこそ全てなら、LispHaskellはその長い歴史に関わらず、現在最もメジャーな言語ではなく、一部のプログラミング愛好家・研究者にしか普及していないのでしょうか。*15

プログラミング言語は問題に対する道具である、という視点からは、目の前の問題について一番都合のよい道具を選ぶべきでしょう。それが悪名高い****言語ということもあるでしょう。

概念を学び、スキルを高めるためなら、必ずしもそうではありません。筋の良い、必ずしも普及していない、きれいな言語を使うのが良いでしょうから。*16

あなたに今本当に必要なものは、たったひとつの至高のプログラミング言語ですか?そのために、他の全てのものを無用といって否定する意味はありませんよね。

惑わされないために

  • 歴史的な経緯を知ること
  • なるべく違った雰囲気の複数の書籍に当たること
    • ゆるふわな「○○言語超速入門!」みたいな本をやるのなら、大学の教科書になっていそうな見た目の「○○の基礎」といった本にも触れること
  • 情報元と、語句の定義を確認すること
  • 跳躍した比喩、こじつけた議論を疑うこと
  • レッテル貼り*17を疑うこと
  • 確認しようのない装飾*18をする文章を疑うこと
  • こういう記事を読み漁ってばかりいないで、良書を読み、自分でコードを書くこと

補足にしては長くなりすぎましたが、以上です。

*1:Schemeでの実装例はメッセージパッシングスタイルですが、目的・用途として「データ型の抽象」等と言っていたため。

*2:参照: 型システム - Wikipedia

*3:例えば以前書いた記事など: FSharperならこう書く(A Poker Game Program) #FsAdvent - 技術memo

*4:要出典。

*5:F#の参照セルは直接的な再代入、Haskell等のStateモナドは大雑把には「手続き的なプログラミングで状態を引き回す手間を削減する」機構です。参照: Haskellでスタックを利用した加減乗除の計算機を作ってみる — laskell 0.0.1 documentation

*6:参照: やさしいFunctional reactive programming(概要編) - maoeのブログ

*7:Reactive-Extensions/RxJS, ReactiveX/RxJavaなど。

*8:言語仕様に対して、リスト処理や非同期処理に個別に固有な構文があるのは気持ち悪い、汎用モナド構文がほしい、等という思いはありますが、それはそれ、これはこれ、長い歴史には負の遺産も付き物です。

*9:参照: 参照透過性 - Wikipedia, 副作用 (プログラム) - Wikipedia

*10:参照: 120901fp key

*11:参照: 遅延評価 - Wikipedia, 遅延評価いうなキャンペーンとかどうか - ぐるぐる~

*12:ここで言う「宣言的」という指標にはある程度眉に唾をつけておいたほうがよいと思います。参照: 宣言的という言葉について - m2ymの雑記帳, 宣言的プログラミングの「宣言的」って? - sh-2の日記

*13:要出典。

*14:要出典。

*15:参照: The Rise of 'Worse is Better', If Lisp Is So Great

*16:LispHaskellが非実用的という意図ではなく、関係者の努力により普及の途上にあり、あるいは例としては少なくても実際に実用されている、という認識です。

*17:ありもしない派閥の存在

*18:海外では広く一般的に普及しその正しさが既に議論されつくしているetcetc...