私は ocaml 初心者です。単純な古い ref を int に、または他の単純な組み込み型を使用することは、これまでのところ、すべての点で期待どおりに機能します。参照がタプルのメンバーであるタプルのコンテキストでそれらを使用しました。参照を更新したり、逆参照したりできます。
# let e = 1, ref 1;;
val e : int * int ref = (1, {contents = 1})
# snd e := 2;;
- : unit = ()
# e;;
- : int * int ref = (1, {contents = 2})
# !(snd e);;
- : int = 2
しかし、"of ref" という名前付きの型を他の集計 (または組み込みの単純な) 型に宣言するとすぐに、事態は一般的にうまくいきません。何かへの「ref」の型として宣言されていなかった以前とは異なり、参照を変更することはできなくなりました。!および := 演算子は失敗します。
そして、セマンティクスは奇妙で一貫性のない方法で変化しているように見えます。以下はほんの一例です。以下の最初のコード ブロックを記述することは合法であるのに、上部のループ (さらに下) で同様のことを行うことは違法であるように見えるのはなぜですか? 最初のブロックはコンパイラによって受け入れられ、構築された型である ref と照合し、! を使用してその値にアクセスできます。これはすべて循環キューのコンテキスト内にあり、#use を使用して一番上のループでファイルからロードされます。
type 'a element = 'a * 'a pointer
and 'a pointer = Pointer of 'a element ref;;
let next (_,n) = n;;
type 'a queue = 'a element option ref;;
let create () = None;;
(*passes compiler and behaves well*)
let enqueue queue x =
match !queue with
None ->
let rec elem = (x, Pointer (ref elem)) in
queue := Some elem;
| Some (_, Pointer last_newest_next) -> (*Insert between newest and oldest*)
let oldest = !last_newest_next in
let elem = (x, Pointer (ref oldest)) in
last_newest_next := elem;
queue := Some elem;;
一番上のループでは、以下のように同様の試み (およびこれに関するバリエーション) が失敗します。関数を使用してタプルを分解し、同じ演算子を呼び出そうとします。
let rec elem = (1, Pointer (ref elem));;
let last = !(next elem);;
Characters 12-22:
let last = !(next elem);;
^^^^^^^^^^
Error: This expression has type int pointer
but an expression was expected of type 'a ref
はい、私は -rectypes を使用していますが、再帰的な省略型を使用せずにこれを一度試してみたかったので、それ以来ずっとそれに固執しています。以下は一番上のループで機能することに注意してください。
let last = next elem;;
val last : int pointer = (* ... *)
そして、最初のコード ブロックが 14 行目で変更され、! オペレーター、壊れます。(以下のように) 書き換えると、エンキュー関数はコンパイラを通過しますが、誤動作します。
(*compiles but fails - que only ever holds one item*)
let enqueue queue x =
match !queue with
None ->
let rec elem = (x, Pointer (ref elem)) in
queue := Some elem;
| Some (_, Pointer last_newest_next) ->
let oldest = last_newest_next in
let elem = (x, Pointer oldest) in
last_newest_next := elem;
queue := Some elem;;
! なしでそれである必要があります。演算子 (および他のいくつかの変更を加えたもの) を使用すると、最後から 2 行目では、(一致の分解された要素内から) 別のポインターを更新して、当初の意図どおりに elem を指すようにするのではなく、実際には elem 内のポインターをそれ自体を指すようにしています。とにかく、型指定された参照のトップループタプル分解とmlファイルから同じことを行うこととの間でセマンティクスが矛盾しているように見える理由はまだわかりません...それがすべての原因である場合. それとも、パターン マッチからの分解は、関数を介してタプルを分解するのと同じではありませんか?!?
そして、デキュー関数を使用して上記の関数の動作をテストしました。
let dequeue queue =
match !queue with
None -> raise Not_found
| Some (_, Pointer oldest_ref) ->
let oldest = !oldest_ref in
let (x, Pointer next_ref) = oldest in
let next = !next_ref in
if next == oldest then
queue := None
else
oldest_ref := next;
x;;
関数型言語で ref セルを避けたい理由は理解できますが、それが不可欠な場合 (しゃれは意図されていません) にそれらを使用する方法を知る必要があります。