最近のインタビューでこんな質問をされました。正しい答えが得られませんでした。
手続き型言語 (C など) と関数型言語 (haskell など) で同じ引数を使用して関数を繰り返し呼び出すと、どちらの言語でも異なる結果が得られる可能性があります。[this] (手続き型プログラミングと関数型プログラミングの違いは何ですか? ) の投稿を読んで、純粋な関数型言語は常に同じ答えになると書いています。手続き型言語ではなく、なぜ関数型言語でそうなるのでしょうか?
最近のインタビューでこんな質問をされました。正しい答えが得られませんでした。
手続き型言語 (C など) と関数型言語 (haskell など) で同じ引数を使用して関数を繰り返し呼び出すと、どちらの言語でも異なる結果が得られる可能性があります。[this] (手続き型プログラミングと関数型プログラミングの違いは何ですか? ) の投稿を読んで、純粋な関数型言語は常に同じ答えになると書いています。手続き型言語ではなく、なぜ関数型言語でそうなるのでしょうか?
命令型プログラミングでは、変数の値の変更、ファイルへの書き込み、ネットワークへのアクセスなど、関数に副作用を持たせることができます。同じ関数を 2 回目に実行すると、前の副作用を検出して何かを返すことができます。違う。簡単な C の例は次のとおりです。
int i = 0;
int foo() {
return i++;
}
これを複数回呼び出すと、異なる番号が生成されます。別の例として:
int foo(int *p) {
return (*p)++;
}
上記を同じパラメーター、つまり同じポインターで呼び出しても、インクリメントのために結果が異なります。
純粋な関数型プログラミングでは、次のような副作用i++
は設計上禁止されています。このように、関数の出力は引数の値のみに依存する必要があります。f
たとえば、Haskell にtypeの関数がある場合、すべての呼び出しで常に同じ整数を返すInt -> Int
と確信できます。f 3
ええと、上記は完全に正しいわけではありません。プログラムは最終的に何らかの I/O を実行する必要があり、さもなければ意味がありません。このため、何らかの形で副作用が利用可能でなければなりません。Haskell では、副作用のある関数は実際には許可されていますが、それらの型はこれらが純粋な関数ではないことを示す必要があります。たとえば、関数g
が typeInt -> IO Int
を持っている場合、I/O を実行し、副作用を持つprint =<< g 3
ことができます: 実行は、命令型プログラミングと同様に、毎回異なる値を出力できます。
つまり、要約すると、純粋な関数型プログラミングでは、副作用のある関数は、そうでない関数とは異なる型を持ちます。多くの場合、適切に設計された関数型プログラムでは、I/O と副作用が必要になることはほとんどなく、純粋な関数がコードの大部分を占めています。このため、ほとんどのコードがこの制約の下で書かれているため、「純粋な関数型プログラミングは副作用を禁止する」と言うことがあります。
純粋関数型言語は、同じ入力が常に同じ出力を生成するように動作し、純粋な関数には副作用がありません。ただし、C などの手続き型言語や純粋に機能的でない言語では、ポインターが変更されるなどの副作用や、時間やファイル I/O などの他の外部要因が発生する可能性があります。Haskell でもファイル I/O を実行できます。したがって、I/O を備えた Haskell が純粋関数型言語である場合、C とcpp
も純粋関数型言語です。
例として、次の C プログラムを取り上げます。
#ifndef _BSD_SOURCE
#define _BSD_SOURCE
#endif
#include <stdio.h>
#include <time.h>
#include <unistd.h>
int get_num(int n)
{
usleep(1100000);
return (time(NULL) - n) % (n / 10);
}
int main(void)
{
int i;
for (i = 0; i < 10; i++)
printf("%d\n", get_num(4242));
return 0;
}
入力の定数パラメータ 、 をとり4242
ますget_num
。任意の計算の後、この手続き型言語で同じ入力が同じ出力を生成しないのは、時間要素とスリープのためです。
一度に実行すると、次のようになります。
247
248
249
250
251
253
254
255
256
257
後で実行すると、次のようになります。
270
271
272
273
274
275
277
278
279
280
ほとんどの C では、副作用がたくさんあります。ただし、純粋に関数型の言語では、そのような副作用は存在せず、可能性もありません。
さらに明確にするために、それは言語の特性ではありません。副作用のある手続きのみを書くことを強制する命令型言語はありません。明示的にサポートされていませんが、純粋な関数型スタイルで書くことも完全に可能です。
一方、純粋な関数型言語では、変数を変更したり、グローバルに表示されるストレージにアクセスしたりできる言語構成要素がまったくありません。したがって、純粋な FP 言語が非純粋な関数を「禁止」しているというのは少し誇張されています。そもそもの書き方。次のような関数でもあることに注意してください。
printTwice x = do {putStrLn x; putStrLn x}
不純ではありません。結果を適用printTwice
すると、IO アクションが発生します。これを何度も行うことができ、結果をリストまたはタプルに入れて渡すことができます。これはすべて純粋です。具体的には、以下の間に違いはありません。
twoActions = (printTwice "hello", printTwice "hello")
と
twoActions = (a,a) where a = printTwice "hello"