1

例を考えてみましょう: 電球をつけたり消したりしたいです。C では、次のように書くことができます。

struct lightbulb {
    int is_turned_on;
    /* from 1 to 10 */
    int light_intensity;
};

電球をオンまたはオフにするときはいつでも、1 (最も暗い) から 10 (最も明るい) にis_turned_on設定することで、1 に変更し、明るさを変更します。light_intensity

関数型プログラミングで同じことを行うにはどうすればよいですか? ONこれらの値を保持するリストを作成し、関数を作成しOFFて電球をオン/オフにし、電球の光の強度を返す関数を作成する必要があると思います。関数が呼び出されるたびに、新しい電球が返されます。

(defun turn-on()
  '(1 0))
(defun turn-off()
  '(0 0))
(defun light-intensity (x)
  `(0 ,(eval x)))

light-intensity のような関数は、線形関数に似た連続関数であることがわかります。xxごとに同じ引数を何度渡しても、同じ結果に評価されます。各関数の結果は、異なる状態の新しい電球です。

問題は、どうすれば状態を永続化できるかということです。明らかに、変数を介してメモリのどこかに保存する必要があります。

更新: c2 Wiki - Functional Programmingを通じて上記の質問に対する回答を見つけました

データ項目はどのように保持されますか?

スタック上。シーケンシャル バッチ プログラムでは、データは最上位関数で初期化および変換されます。サーバーのような長期間存続するプログラムでは、最上位のループ関数が再帰的に呼び出され、ある呼び出しから次の呼び出しにグローバル状態が渡されます。

また、関数が呼び出されるたびに新しいオブジェクト (リスト) を作成する必要があります。以前の古いオブジェクトを破棄するにはどうすればよいですか?

defparameterandを介して変数を変更する方が効率的で簡単ではありませんsetfか? それが電球ではなく、より多くの情報を持つより複雑なオブジェクトであると想像してみてください。これを関数としてモデル化するにはどうすればよいですか?

4

2 に答える 2

2

問題は、どうすれば状態を永続化できるかということです。明らかに、私はそれを変数を通して私の記憶のどこかに保存しなければなりません。

関数型プログラミングを命令型の観点から見ていると思いますが、これは頻繁に発生し、混乱を招く可能性があります。関数型プログラミングでは、プログラムを(たとえば変数を設定することによって)状態を変更する一連のステップとして表すのではなく、それぞれが1つの式のみを含む相互依存の数学スタイルの関数のセットとして表されます。関数に複数の行を含める唯一の理由は状態を変更することであるため、純粋関数型プログラミングでは、すべての関数はワンライナーです。これは、コードがカスケード一連の関数呼び出しとして実行されることを意味します。プログラムは、段階的な説明というよりも、問題の説明のように見られる傾向があります。

また、関数が呼び出されるたびに新しいオブジェクト(リスト)を作成する必要がありますが、以前の古いオブジェクトを破棄するにはどうすればよいですか?

関数型プログラミング言語はすべてガベージコレクションを使用していると思います。副作用とメモリエイリアシングが少ないということは、メモリが使用されなくなったときに簡単に解決できることを意味します。

defparameterとsetfを使用して変数を変更する方が、より効率的で簡単ではありませんか?それが電球ではなく、はるかに多くの情報を備えたより複雑なオブジェクトであると想像してみてください。これを関数としてモデル化するにはどうすればよいですか?

ここで何を求めているのかわかりません。

于 2012-11-01T10:29:21.367 に答える
1

「参照透過性」にするためだけに別のコピーを作成せずに、純粋関数型プログラミングで状態を効率的に処理する方法を尋ねています。

言語が線形型をサポートしていれば、関数型プログラミングで状態を効率的に処理できます。つまり、各可変セルには線形型が与えられ、タイプチェッカーは、線形型の変数がプログラマーの意志で破棄または複製されていないことを確認します。たとえば、これは許可されていません。

val x = make_reference (5) // [x] is a mutable cell
val y = x
val res = !x + !y // the syntax [!x] is for reading a value of a cell

[x]には線形型があり、線形型の値を複製できないため、これは許可されていません(これは、[y]を[x]にバインドするときに、基本的に次の行で行うことです)。この種の重複は「エイリアシング」(または「共有」)とも呼ばれ、エイリアシングは、状態操作プログラムを推論するのを困難にするものです(たとえば、参照透過性を壊すことによって)。したがって、線形型はエイリアシングを制限し、それはプログラムについて推論するのに役立ちます。ほとんどの場合、線形型のプログラムは参照透過性であり、純粋関数型プログラムとの類似性を保持しています。

これは、(可変の)状態を処理するために線形型を使用するATSの例です。

typedef lightbulb (b: bool) = @{is_turned_on= bool b, light_intensity= intBtw (1, 10)}

fn lightbulb_make {b:bool} (state: bool b, intensity: intBtw (1, 10)) :<> lightbulb b =
  @{is_turned_on= state, light_intensity= intensity}

// The [&T1 >> T2] notation means that function expects to be given
// a term of type T1, and then on exit, the type of the term will
// change to T2.
// In our case, the function expects a lightbulb either turned on or off,
// but on exit, the lightbulb will be turned off.
fn lightbulb_turn_on {b:bool} (x: &lightbulb b >> lightbulb true) :<> void =
  x.is_turned_on := true

fn lightbulb_change_intensity {b:bool} (x: &lightbulb b, y: intBtw (1, 10)) :<> void =
  x.light_intensity := y

implement main () = let
  var bulb = lightbulb_make (false, 5)
  val () = lightbulb_turn_on (bulb)
  val () = lightbulb_change_intensity (bulb, 3)
in
  printf ("intensity is now: %d\n", @(bulb.light_intensity))
end
于 2012-11-01T10:29:02.117 に答える