1

私は 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 セルを避けたい理由は理解できますが、それが不可欠な場合 (しゃれは意図されていません) にそれらを使用する方法を知る必要があります。

4

2 に答える 2

5

あなたが書いたものから特定の質問を見つけるのは難しい. しかし、OCaml は一貫性がなく、非論理的でもありません。これは、「現代の」FP 言語の優れた点の 1 つです。その型システムは健全な数学に基づいています。今のところ、最初に示した、機能しないものに焦点を当てます。

# let rec elem = (1, Pointer (ref elem));;
# let last = !(next elem);; ## TYPE ERROR HERE

何が何であるかを見るだけで、問題はかなり明確なようですnext elem。の定義からわかるようにelemnext elemPointer (ref elem)です。これは参考になりません。そのコンストラクタはPointer. したがって、演算子をそれに適用しても意味がありませ!ん。これは、型エラーが伝えていることです。elemを元に戻したい場合は、コンストラクターnext elemを分解する必要があります。Pointer

# let unpointer (Pointer x) = x;;
# let last = !(unpointer (next elem));;
# last == elem;;
- : bool = true
# 

編集:価値があるのは、あなたの循環リストタイプは私には少し複雑に見えます. 循環リストが本当に必要な場合は、OCaml に含まれるバッテリー: BatDllistにある双方向リンク リストの実装を参照してください。これは、C で記述したものと非常によく似た単純な低レベルの実装です。組み込みの OCaml リストを使用するとさらに良いでしょう! 長年の OCaml コーディングで循環リストを使用する必要性を感じたことはありません (1 つのデータ ポイントのみ)。

于 2012-12-27T06:53:36.603 に答える
4

あなたはプログラミング言語のセマンティクスについて非常に低レベルの理解を持っているようです。この場合、低く考えることを主張することは、あなたを迷わせているようです。OCaml の参照のセマンティクスは一貫しており、ここで見られる奇妙なことはすべて、言語のセマンティクスではなく、あなたのミスによるものです。

それがあなたの助けになるなら、実装レベルの低い人に OCaml のセマンティクスを説明する方法があります。これは、私が通常初心者に説明する方法ではありませんが、ポインター、集約、およびポインターの観点から考えることに固執する場合は、次のようになります。

  • OCaml の値は、整数で表現可能で不変であり、そのまま渡されるか、ヒープ ブロックへのポインタのいずれかです。可変性は、この設定ではより簡単に推論できます。値による/参照による区別を行う言語では、明示的に分解または再構築されない限り、すべてが共有されます。

  • 参照は、変更可能なフィールドを持つレコードとして定義された派生概念type 'a ref = {mutable contents: 'a};です。これは、値を指し、それが指す値を変更できる単純なボックスに対応します。ポリモーフィックであり、一貫した方法で動作します。特に、あなたが観察した入力エラーはコードのエラーによるものです。もっとよく見てください!

  • タイプtype foo = Foo of tは とは異なりtます。a をwithに変換しv : tたり、 aをfoowithFoo vに変換したりできます。v : foot(match v with (Foo x) -> x)

同時に多くの困難を混ぜすぎていると思います。ドロップ-rectypesしてコードを機能させてから、コードを軽量化できるかどうかを確認するために追加することを検討できます。-rectypesがデフォルトで有効になっていない理由は、確かにエラー ( let sum x = x (* + *) x) である一部のコードが受け入れられ、後でそれを使用するときに不可解なエラー メッセージが表示されるためです。言語の他の側面に不快感を覚える場合は、そうなってほしくありません。

于 2012-12-27T08:35:37.780 に答える