HP や位置 (3d ベクトル) などの変更可能なプロパティ、init、setter、getter などの関数を使用して「プレーヤー」クラスを表す方法の例を教えてください。
3 に答える
真の慣用的な Clojure では、「プレーヤー」は不変であり、おそらくそれをマップとして表すことになります。たとえば、次のようになります。
{:type :player
:team :red
:hit-points 10
:location [17 9 6]}
プレーヤーはより大きな「ワールド」データ構造に含まれている可能性があり、update-world
必要な変更 (プレーヤーを新しい位置に移動するなど) を加えて新しいワールドを作成する純粋な関数が存在する可能性があります。
ゲッター/セッターについては、通常のマップ操作関数を使用してください。標準の Clojure データを操作するだけの場合は、多くの場合、ゲッター/セッターは必要ありません。
レコードを定義することもできます。これは、マップと同様のインターフェイスとセマンティクスを持ちますが、いくつかの利点があります。たとえば、レコード内のメンバーへのアクセスは、マップ内よりも高速です。さらに、レコード上でプロトコルを拡張し、これを使用してレコード上で高速なポリモーフィック ディスパッチを行うことができます。たとえば、描画プロトコルをさまざまな形状オブジェクトに拡張できます。Rich Hickey 氏 (http://www.infoq.com/interviews/hickey-clojure-reader#) によると、プロトコルを扱うことは Clojurescript コンパイラーで彼らを助けました。
例えば
(defrecord Action [time key args state]
(comment protocol extension can go here))
私はmikeraに同意しますが、あなたは不変にそれをやろうとするべきですが、あなたは特に可変プロパティを要求するので、私が提案する方法はmikeraの答えと非常に似ていますが、可変プロパティを持つマップ内でアトムを使用するには、変更したい。
(def player1
{:type :player
:team :red
:hit-points (atom 10)
:location (atom [17 9 6])})
変更したい部分だけがアトムでラップされていることに注意してください。変更可能なデータにアクセスするには、次のように逆参照する必要があります。
@(player1 :hit-points)
10
swap!
またはを使用して値を設定するには、次のreset!
ようにします。
(swap! (player1 :hit-points) dec)
9
@(player1 :hit-points)
9
(reset! (player1 :hit-points) 2)
2
@(player1 :hit-points)
2
これは、init、getter、setter のようなものを要求しましたが、1 つのプレーヤーを作成する例です。現時点では、Clojure 以外でのプログラミング経験はほとんどないので、Clojure がどのようなものになるかを完全に把握していない可能性がありますが、ここでは、Clojure をセットアップする方法を説明します。
(defn new-player
[hit-points location]
{:type :player
:team :red
:hit-points (atom hit-points)
:location (atom location)})
次に、新しいプレーヤーを作りたいときは、次のようにします。
(def my-player
(new-player 20 [0 0 0]))
{:type :player
:team :red
:hit-points #<Atom@1959415: 20>
:location #<Atom@12d0e49: [0 0 0]>}
明示的な「ゲッター」および/または「セッター」を作成する必要はないと思います。なぜなら、変更可能なデータを逆参照することで簡単に取得でき、変更可能なデータを上記で、swap!
またはreset!
まったく同じ方法で設定できるからです。そうは言っても、必要に応じて、次のようなことができます。
(defn get-hp
[player]
@(player :hit-points))
(defn get-loc
[player]
@(player :location))
(defn set-hp
[player new-hp]
(reset! (player :hit-points) new-hp))
(defn set-loc
[player new-loc]
(reset! (player :location) new-loc))
これらを使用して、次のことができます。
(get-hp my-player)
20
(get-loc my-player)
[0 0 0]
(set-hp my-player 17)
17
(get-hp my-player)
17
(set-loc my-player [0 1 1])
[0 1 1]
(get-loc my-player)
[0 1 1]
この回答はまだ十分に長くないため、新しいプレーヤーを作成するときにデフォルト値を含めるとよいと思います。これを行うための簡単な方法を考えることができますが、必ずしもエレガントであるとは限りません。
(defn new-player
([]
(new-player 20 [0 0 0]))
([hp-or-loc]
(cond
(integer? hp-or-loc) (new-player hp-or-loc [0 0 0])
(vector? hp-or-loc) (new-player 20 hp-or-loc)
:else (throw (Exception. "Value must be either integer for hp or vector for location."))))
([hp loc]
{:type :player
:team :red
:hit-points (atom hp)
:location (atom loc)}))
デフォルトでは、新規プレイヤーは 20 HP を持ち、ロケーション [0 0 0] にいます。整数またはベクトルのいずれかが渡された場合、それが (それぞれ) hp または位置の値であると想定するか、そうでない場合は例外をスローします。
繰り返しになりますが、ほとんどの場合、変更可能なデータはおそらく不要であると思います。最も簡単な解決策は、「この変更可能なデータ構造をどのように作成できるか」ではなく、「新しい更新されたバージョンの不変のデータを作成し、これらのデータをループの先頭に戻します。その後、更新して再び繰り返すことができます。」
うまくいけば、これのいくつかは役に立ちます。