3

いくつかの簡単なコマンドを使用して、OCaml でインタラクティブな電卓を作成しています。ユーザーは、たとえば、独自の単純な関数 (数学関数) を定義できる必要があります。

let f(x) = x
let g(x) = 2*f(x)

現在、関数は関数型言語のように処理する必要があります。つまり、作成時の環境を記憶する必要があります。つまり、関数では、関数と変数であるその環境のクロージャを維持する必要があります。

のように形成されたタプルのリストに、現在定義されている関数を保持します(functions_present_at_the_time_of_creation, variables_present_at_the_time_of_creation, function_name, function_argument_names, function_formula)。関数のリストに新しい関数を追加しようとすると (現在定義されておらず、何も上書きする必要がないと仮定しましょう)、関数のリストの最後まで繰り返し繰り返し、そこに追加したいと思います新しいタプル。

問題は、現在の関数リストのタイプが であると仮定して、(a*b*c*d*e) listそれ自体を含むタプルを最後に追加しようとすると、そのタイプが に変更されること((a*b*c*d*e) list*f*g*h*i) listです。タプルにカプセル化されたリストをそれ自体に追加するにはどうすればよいですか?

この問題の回避策を見つけようとして書いた簡単な SSCCE を次に示します。

let rec add_to_end list list_copy dummy = match list with
| [] -> [(list_copy, dummy)]
| h::t -> h::(add_to_end t list_copy dummy) 

let add list dummy = add_to_end list list dummy

これは、リストのコピーを使用してそれを実行しようとします。次の例は、コピーを使用せずに記述されています (もちろん、これらの例はどちらも機能しません)。

let rec add_to_end list dummy = match list with
| [] -> [(list, dummy)]
| h::t -> h::(add_to_end t dummy) 

最初の例は、関数 add を使用しようとすると機能しませんが、たとえば次のように (インタープリターで) 実行すると機能します。

let l = [];;
let l = add_to_end l l 1;;
let l = add_to_end l l 2;;
let l = add_to_end l l 3;;

その後、正常に動作します。デザインの変更も考えているかもしれませんが、どんな提案でも大歓迎です。

編集:上記のコマンドの出力は次のとおりです。

# let l = [];;
val l : 'a list = []
# let l = add_to_end l l 1;;
val l : ('a list * int) list = [([], 1)]
# let l = add_to_end l l 2;;
val l : (('a list * int) list * int) list = [([], 1); ([([], 1)], 2)]
# let l = add_to_end l l 3;;
val l : ((('a list * int) list * int) list * int) list =
[([], 1); ([([], 1)], 2); ([([], 1); ([([], 1)], 2)], 3)]
4

1 に答える 1

3

OCaml リストが不変であることを認識しているかどうかを判断するのは困難です。既存のリストの末尾に値を追加することはできません。既存のリストは決して変更できません。最後に値を追加して、新しいリストを作成できます。これを行うと、リストと新しい値で構成されるペアを末尾に追加する理由がわかりません。私はあなたがそれについて間違って考えているのではないかと疑っています。リストと整数を取り、リストの最後に整数を追加する関数を次に示します。

# let rec addi i list =
    match list with
    | [] -> [i]
    | h :: t -> h :: addi i t
  ;;
val addi : 'a -> 'a list -> 'a list = <fun>
# let x = [1;2;3];;
val x : int list = [1; 2; 3]
# addi 4 x;;
- : int list = [1; 2; 3; 4]
# x;;
- : int list = [1; 2; 3]
# 

この関数は、末尾に値が追加された新しいリストを返します。元のリストは変更されません。

余談ですが、リストの先頭に値を追加する方がはるかに慣用的です。リストの末尾に繰り返し追加すると、速度が低下します。2 次動作になります。他の順序が必要な場合は、通常、すべてを前に追加してから、リストを逆にします。これは依然として直線的です。

編集

どうやら、次のような関数が本当に必要なようです。

let fa list = list @ [(list, a)]

これは現実的には不可能であり、型が正しく機能しません。リストには、1 つのタイプのみを含めることができます。tしたがって、リストの型は type と同じであると結論付けることができます。(t, v) listここvで、 は a の型です。これは再帰的なタイプであり、実際に使用したいものではありません (IMHO)。

OCaml で実際にこの型を取得するには、次のようにし-rectypesます。

$ ocaml -rectypes
        OCaml version 4.00.0

# let f a list = list @ [(list, a)];;
val f : 'a -> (('b * 'a as 'c) list as 'b) -> 'c list = <fun>
# 

しかし(私が言うように)それは私が避けたいことです。

編集 2

見てみると、最初のコード サンプルでは、​​リストの 2 つの異なるコピーを指定しているため、再帰型が不要になっています。同じリストで関数を呼び出すまで、これらは異なるタイプである可能性があります。したがって、関数型は再帰的ではありません。同じリストの 2 つのコピーを使用して呼び出すと、リストの型とは異なる型の新しい値が作成されます。l異なる値(異なるタイプ)に同じ名前を使用しているためにのみ機能します。リストを表す単一の型が必要な実際のプログラムでは機能しません。

別の補足的なコメントとして、リストの先頭に値を追加することの利点は、リストの古い値がまだそこにあることです。新しいリストの末尾です。これは、実際にやりたいことにより近いようです

于 2012-11-07T21:35:47.193 に答える