この答えの目的のために、「純粋に機能的な言語」とは、機能が参照的に透過的である機能的な言語を意味します。これは、純粋に機能的な言語の通常の定義だと思います。
純粋な関数型プログラミング言語では副作用がありません(したがって、実用的なプログラムは副作用を持っているため、実際にはほとんど役に立ちません。たとえば、外部の世界と対話するときです)。
参照の透明性を実現する最も簡単な方法は、実際に副作用を禁止することであり、実際にそうなる言語があります(主にドメイン固有のもの)。しかし、それは確かに唯一の方法ではなく、最も汎用的な純粋に関数型の言語(Haskell、Cleanなど)が副作用を許容します。
また、副作用のないプログラミング言語は実際にはほとんど使用しないと言っても、本当に公平ではないと思います-確かにドメイン固有の言語ではなく、汎用言語であっても、副作用を提供せずに言語が非常に役立つ可能性があると思います。コンソールアプリケーション用ではないかもしれませんが、GUIは、機能的なリアクティブパラダイムなどの副作用なしにうまく実装できると思います。
ポイント1に関しては、純粋に機能的な言語で環境と対話できますが、それらを導入するコード(関数)を明示的にマークする必要があります(たとえば、Haskellではモナド型を使用)。
それはそれを単純化することを少し上回っています。(C ++のconst-correctnessに似ていますが、一般的な副作用がある)副作用のある機能をマークする必要があるシステムがあるだけでは、参照の透明性を確保するには不十分です。プログラムが同じ引数を使用して関数を複数回呼び出せず、異なる結果を取得できないようにする必要があります。次のようにすることでそれを行うことができますreadLine
関数ではないもの(HaskellがIOモナドで行うこと)または同じ引数で副作用関数を複数回呼び出せないようにすることができます(それはCleanが行うことです)。後者の場合、コンパイラは、副作用のある関数を呼び出すたびに新しい引数を使用して呼び出しを行い、同じ引数を副作用のある関数に2回渡すプログラムを拒否します。
純粋な関数型プログラミング言語では、状態を維持するプログラムを作成できません(多くのアプリケーションでは状態が必要なため、プログラミングが非常に厄介になります)。
繰り返しになりますが、純粋に関数型の言語は可変状態を許可しない可能性がありますが、上記の副作用で説明したのと同じ方法で実装すると、純粋で可変状態を維持することは確かに可能です。本当に可変状態は、副作用の単なる別の形です。
そうは言っても、関数型プログラミング言語は間違いなく可変状態を阻止します-特に純粋なものはそうです。そして、それがプログラミングを厄介なものにするとは思わない-まったく逆です。時々(しかし、それほど頻繁ではありませんが)可変状態は、パフォーマンスや明確さを失うことなく避けられません(Haskellのような言語には可変状態のための機能がある理由です)。
それらが誤解である場合、どのようにして生じたのですか?
多くの人々は単に「関数は同じ引数で呼び出されたときに同じ結果を生成しなければならない」と読み、readLine
可変状態を維持するようなコードやコードを実装することは不可能であると結論付けていると思います。そのため、純粋に関数型の言語が参照の透明性を損なうことなくこれらのことを導入するために使用できる「チート」を単純に認識していません。
また、可変状態は関数型言語では非常に落胆するため、純粋に関数型の言語ではまったく許可されていないと想定するのはそれほど大きな飛躍ではありません。
(1)副作用を実装し、(2)状態で計算を実装するHaskellの慣用的な方法を示す(おそらく小さな)コードスニペットを記述できますか?
これは、ユーザーに名前を尋ねて挨拶するPseudo-Haskellのアプリケーションです。Pseudo-Haskellは、HaskellのIOシステムを備えた、私がちょうど発明した言語ですが、より一般的な構文、より説明的な関数名を使用し、-表記はありませdo
ん(IOモナドが正確にどのように動作するかをそらすため):
greet(name) = print("Hello, " ++ name ++ "!")
main = composeMonad(readLine, greet)
手がかりはここつまりreadLine
型の値であるIO<String>
とcomposeMonad
タイプの引数を取る関数であるIO<T>
(いくつかのタイプのためにT
)とタイプの引数を取る関数である別の引数T
と型の値を返すIO<U>
(一部のタイプのためにU
)。print
文字列を取り、typeの値を返す関数ですIO<void>
。
typeの値は、type IO<A>
の値を生成する特定のアクションを「エンコード」する値ですA
。composeMonad(m, f)
新しい生成IO
のアクションコード値m
の作用に続いてf(x)
、x
値のアクションを実行することによって生成されますm
。
可変状態は次のようになります。
counter = mutableVariable(0)
increaseCounter(cnt) =
setIncreasedValue(oldValue) = setValue(cnt, oldValue + 1)
composeMonad(getValue(cnt), setIncreasedValue)
printCounter(cnt) = composeMonad( getValue(cnt), print )
main = composeVoidMonad( increaseCounter(counter), printCounter(counter) )
以下mutableVariable
は、任意の型の値を取りT
、を生成する関数ですMutableVariable<T>
。この関数getValue
は、現在の値を生成するを取得してMutableVariable
返しIO<T>
ます。setValue
とを取り、値を設定MutableVariable<T>
するT
を返しIO<void>
ます。composeVoidMonad
は、composeMonad
最初の引数がIO
意味のある値を生成しないであり、2番目の引数が別のモナドであり、モナドを返す関数ではないことを除いて同じです。
Haskellには、この全体の試練の痛みを軽減する構文糖がありますが、可変状態は、言語が本当に望んでいないものであることは依然として明らかです。