ProgramHaskellHaskell入門 > 第1章 はじめてのHaskell

2022-01-24 (月) 公開
学習メモ
 Haskell入門 関数型プログラミング言語の基礎と実践
本間 雅洋
技術評論社
2017-09-27
¥3608

1.1.3 純粋性

すべての関数が「参照透過性」を持つような関数型プログラミング言語を、「純粋関数型プログラミング言語」と言います。
Haskellは、純粋関数型プログラミング言語です。

参照透過性

(参考)参照透過性 - Wikipedia https://w.wiki/4hfs

参照透過性(さんしょうとうかせい、英: Referential transparency)は、計算機言語の概念の一種である。
ある式が参照透過であるとは、その式をその式の値に置き換えてもプログラムの振る舞いが変わらない(言い換えれば、同じ入力に対して同じ作用と同じ出力とを持つプログラムになる)ことを言う。
具体的には変数の値は最初に定義した値と常に同じであり、関数は同じ変数を引数として与えられれば同じ値を返すということになる。
当然変数に値を割り当てなおす演算である代入 (Assignment) を行う式は存在しない。
このように参照透過性が成り立っている場合、ある式の値、例えば関数値、変数値についてどこに記憶されている値を参照しているかということは考慮する必要がない、即ち参照について透過的であるといえる。

Haskellでは、

  • すべての変数はイミュータブル(変更不能)であり、代入による変更はできません。
  • I/Oを発生させるには「IOモナド」という仕組みを明示的に使う必要があります。

副作用

(参考)副作用 (プログラム) - Wikipedia https://w.wiki/4jC4

プログラミングにおいて、式の評価による作用には、主たる作用とそれ以外の副作用(side effect)とがある。
式は、評価値を得ること(※関数では「引数を受け取り値を返す」と表現する)が主たる作用とされ、それ以外のコンピュータの論理的状態(ローカル環境以外の状態変数の値)を変化させる作用を副作用という。
副作用の例としては、グローバル変数や静的ローカル変数の変更、ファイルの読み書きなどのI/O実行、などがある。

1. 同じ条件を与えれば必ず同じ結果が得られる
2. 他のいかなる機能の結果にも影響を与えない
このような性質を参照透過性という。
参照透過な機能はそれ自身状態を持たないことで、副作用とは縁がない。

副作用を前提とするノイマン型のアーキテクチャ、つまり、大半のプログラミング言語では、グローバル変数への破壊的代入、参照渡しされた引数に対するいわゆる“破壊的操作”、そしてインスタンス(レシーバ)に対する破壊的メソッドなどがある。
一方、関数型言語では原則として副作用を存在しないものとみなし、モナドなどの手法で抽象化している。

機能が副作用を持たないことの利点は、いかなる状況でも常に同じ結果が得られるために数理論理学に基づく形式的な検証ができ、状況依存でのバグの発生が抑えられ、宣言型プログラミングができるということである。
反面、副作用を持たない言語設計はノイマン型アーキテクチャと反りが合わず、効率の点で不利になることが多い。
また、単純な逐次処理を行う場合は状態を中心に命令的な思考をした方が扱いやすい場合がある。
このためLISPやMLなどは原則として関数型ながら、副作用を許容する設計になっている。

(参考)参照透過性 - Wikipedia https://w.wiki/4hfs

Haskellではモナド (Monad) 型と構文糖衣を利用して参照透過性を保ったまま手続き型的な表現を可能にしている。

参照透過性の解釈の変化
Haskellでは上述のような問題点を解決するための試みがなされ、新しい仕様であるHaskell 2010でSTモナドとして結実した。
参照透過性を満たす関数は引数が同じなら同じ値を返すものであるから、代入を使ってもその性質を満たし、なおかつ他の計算にも影響しなければよいとするものである。
わかりやすい例として再帰関数がある。
合計や階乗などは代入を使った反復であっても、与えられた引数が同じなら結果も同じである。
代入をその関数の内部だけにとどめ、結果の取得は変数の内容をコピーして返す関数を使わせることで、確実に副作用から切り離すといった工夫を盛り込んだ。
これがSTモナド(State Transformerモナド)である。

Haskellでは関数の「参照透過性」を維持するために、I/Oなどで発生する副作用(Side effect)を分離(管理)する仕組みが用意されています。
それが「モナド」です。

モナドを書きやすくするための糖衣構文として「do構文」なども用意されているので、副作用を活用する命令型言語と同じように手軽にI/Oを記述できます。

並列処理

参照透過性は関数が状態を持たないことを保証します。
状態を持たない数学的な関数は、並列処理を実現するおに適しています。

HaskellのI/Oの仕組み

関数型プログラミングでは、参照透過性が保証された関数を組み合わせてプログラムを作ります。
しかし、それだけでは「副作用」を扱えません。
すなわち、ファイルの読み書き、ディスプレイへの文字表示などのように、システムの外界とやりとりが必要な処理を直接は実現できません。
このようなプログラムとシステムの外界とのやりとりを、I/O(入出力)と呼びます。

I/Oのないプログラムでは、実用的な処理ができません。
現実の課題を解決するには、キーボードからの入力、ファイルの読み書き、画面への出力など、システムの外界とのやりとりが必要です。

関数型言語では、どのようにI/Oを実現しているのでしょうか?

非純粋な関数型言語のI/O実現

評価と当時にI/Oが発生する関数を設けることでI/Oを実現しています。

(例)F#の場合(F#はMicrosoft製の関数型言語でOCamlをベースにしている)

println "Hi."

という式を評価すると
()
という値が返り、画面に「Hi.」という文字列を表示するI/Oが発生します。

HaskellのI/O実現

Haskellでは、F#のように評価と同時にI/Oが発生する関数はありません。

  1
println "Hi."

という式を評価すると IO ()型の値が返されますが、この時点では画面には何も表示されません。
返されたIO ()型の値が、Haskellの処理系によって解釈されてはじめて画面に「Hi.」の文字が出力されます。

Haskellでは、I/Oが行われる式にはすべて「IO」という型が付きます。
逆に、IOという型が付かない式ではI/Oが発生しないため、参照透過性が崩れる心配がありません。

まとめ

この辺りの説明は、文字通りに受け止めておくけど、いまいち納得できていないかも?(なんかモヤモヤする。もっと具体例を挙げた説明が必要かも)
後で深掘りして、理解を深めたいです。
とりあえず、今の段階ではそういうもんだと受け流して、先へ進んでみましょう。

愚痴

だいたいさ、一度値を決めてしまったら後で変更不能なら、他の命令型プログラミング言語だとそれは「変数」じゃなくて「定数」と呼ぶじゃん。
言葉の問題でしかないけど、変更不可能なのに「変数」と名乗るのは変でしょ?
初心者だと余計な混乱を来たすと思うので、ちょっと不親切だと思ったね。

だから、変数に値を「代入」とは言わないで、変数に値を「束縛」なんて言い方をしてる。
それなら、最初からHaskellには定数しかなくて変数はない、っていう仕様にしてもらった方が分かりやすいと思うんだよね。
細かいことだけど、こういうのもモヤモヤの原因になってる。
まあ、いいけど。

  • Haskell 超入門 - Qiita
    https://qiita.com/7shi/items/145f1234f8ec2af923ef

    束縛
    Haskellの変数は後で別の値を再代入することができません。そのため代入ではなく束縛という用語を使います。
    ※ 値が変更できないので変数は存在しないという見解もありますが、ここでは変数は必ずしも値が変更できることを意味しないという立場を取ります。(参考1、参考2)

  • https://twitter.com/igrep/status/753946652352708609

    「Haskellに変数はない!」の本当の意味は「Haskellには『再代入可能』という意味での変数がないだけであってもっと一般的な『なんか変わりうるもの』という程度の意味の変数は普通に使うよ!」なので「変数」って出てきたからって慌てないでください。

それなら「再代入のできる値を保持する構造」を何て呼べばいいのさ?

  • 変数 (プログラミング) - Wikipedia
    https://w.wiki/4XL4

    プログラミングにおける変数(へんすう、英: variable)とは、高水準言語のプログラムのソースコードにおいて、扱うデータを読み書きする記憶域 (storage) のことであり、固有の名前(識別子)によって識別される。
    変数を用いることで、データを一定期間記憶し必要なときに利用することができる。
    高水準言語において、変数は記憶装置(メモリ)を抽象化する役割を果たす。

    一人一人の人間が異なる名前によって区別されるように、変数も個々の名前によって区別される。
    これにより、プログラム上で複数のデータを容易に識別・管理することができる。
    変数の識別子 (identifier) のことを変数名 (variable name) という。
    一般に、変数が表すデータをその変数の値(あたい、英: value)という。

  • variable とは 意味・読み方・表現 | Weblio英和辞書
    https://ejje.weblio.jp/content/variable

    変わりやすい、変化しやすい、移り気な、変えられる、変動できる、可変的な、変数の、不定の、変異する

「variable」という英語には、「可変の」という意味があるから、「変数」を変化しない値の構造というのは無理があるでしょ。
命令型言語がこれだけ普及した現代で「変数」の定義を変えるよりも、別の用語を用意してもらった方が現実的だと思いますよ。

理路に矛盾があると思うわけよ。
実際、Haskellには変須がなくて定数しかなくても困らないなら、それでいいじゃんね?
もうちょっと先に進んでから提言してみるわ。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2022-01-24 (月) 04:26:53 (119d)