タグ付き共用体およびバリアントレコードとも呼ばれる合計タイプをどのように表現しますか?Either a b
Haskellや
Either[+A, +B]
Scalaのようなもの。
Either
2つの用途があります。2つのタイプのいずれかの値を返すか、タグに基づいて異なるセマンティクスを持つ必要がある同じタイプの2つの値を返すことです。
最初の使用は、静的型システムを使用する場合にのみ重要です。
Either
Haskell型システムの制約を考えると、基本的に可能な最小のソリューションです。動的型システムを使用すると、必要な任意の型の値を返すことができます。Either
必要ありません。
2番目の使用法は重要ですが、2つ(またはそれ以上)の方法で非常に簡単に実行できます。
{:tag :left :value 123} {:tag :right :value "hello"}
{:left 123} {:right "hello"}
私が確認したいのは、:tagは常に存在し、指定された値の1つのみを取ることができ、対応する値は一貫して同じタイプ/動作であり、nilにすることはできないということです。簡単な方法があります。コード内のすべてのケースを処理したことを確認してください。
これを静的に確認したい場合、Clojureはおそらくあなたの言語ではありません。理由は単純です。式は実行時まで、つまり値を返すまで型を持ちません。
マクロが機能しない理由は、マクロの展開時にランタイム値がないため、つまりランタイムタイプがないためです。シンボル、アトム、sexpressionsなどのコンパイル時の構成があります。eval
それらは可能ですが、使用することeval
は多くの理由で悪い習慣と見なされます。
ただし、実行時にかなり良い仕事をすることができます。
- 私が確認したいのは、:tagが常に存在するということです。
- そしてそれは指定された値の1つだけを取ることができます
- 対応する値は一貫して同じタイプ/動作です
- ゼロにすることはできません
- そして、私がコード内のすべてのケースを処理したことを確認する簡単な方法があります。
私の戦略は、通常は静的なもの(Haskellの場合)をすべて実行時に変換することです。コードを書いてみましょう。
;; let us define a union "type" (static type to runtime value)
(def either-string-number {:left java.lang.String :right java.lang.Number})
;; a constructor for a given type
(defn mk-value-of-union [union-type tag value]
(assert (union-type tag)) ; tag is valid
(assert (instance? (union-type tag) value)) ; value is of correct type
(assert value)
{:tag tag :value value :union-type union-type})
;; "conditional" to ensure that all the cases are handled
;; take a value and a map of tags to functions of one argument
;; if calls the function mapped to the appropriate tag
(defn union-case-fn [union-value tag-fn]
;; assert that we handle all cases
(assert (= (set (keys tag-fn))
(set (keys (:union-type union-value)))))
((tag-fn (:tag union-value)) (:value union-value)))
;; extra points for wrapping this in a macro
;; example
(def j (mk-value-of-union either-string-number :right 2))
(union-case-fn j {:left #(println "left: " %) :right #(println "right: " %)})
=> right: 2
(union-case-fn j {:left #(println "left: " %)})
=> AssertionError Assert failed: (= (set (keys tag-fn)) (set (keys (:union-type union-value))))
このコードは、次の慣用的なClojureコンストラクトを使用します。
- データ駆動型プログラミング:「タイプ」を表すデータ構造を作成します。この値は不変でファーストクラスであり、ロジックを実装するために使用できる言語全体があります。これは、Haskellができるとは思わないことです。実行時に型を操作します。
- マップを使用して値を表す。
- 高次プログラミング:fnsのマップを別の関数に渡します。
Either
ポリモーフィズムに使用している場合は、オプションでプロトコルを使用できます。それ以外の場合、タグに興味がある場合は、形式の何かが{:tag :left :value 123}
最も慣用的です。あなたはしばしばこのようなものを見るでしょう:
;; let's say we have a function that may generate an error or succeed
(defn somefunction []
...
(if (some error condition)
{:status :error :message "Really bad error occurred."}
{:status :success :result [1 2 3]}))
;; then you can check the status
(let [r (somefunction)]
(case (:status r)
:error
(println "Error: " (:message r))
:success
(do-something-else (:result r))
;; default
(println "Don't know what to do!")))