問題の核心は、型変数をパラメータとして残して、必要に応じてそれらのパラメータにクラス制約を設定したいということですが、その型は実在するように量的にしたい)。
ここで利用できる簡単なことは、TypeKey x ~ a
を満たすタイプのセットが有限であることです。これをタイプファミリで表現する代わりに、次の表現を考えてみましょう:
たとえば、次のような一般的な有限マップを表すことができます。 *
〜Symbol
ですが、簡単にするために具体的にしておきます。
あなたは現在、非常に簡単に型キーについて様々なことを「証明」機能書き込むことができます。
type IsTypeKey a x = (ToJSON a, FromJSON a, KnownSymbol x)
isTypeKey :: TypeKeyOf a x -> (IsTypeKey a x => r) -> r
isTypeKey IntK k = k
isTypeKey StringK k = k
keyOf :: TypeKeyOf a x -> Proxy x
keyOf _ = Proxy
決定的に、あなたのタイプのクラスのインスタンスは、コンテキストを持つべきではない - あなたのタイプの情報はすべて非表示になります。
instance ToJSON (TypeKeyOf a x) where
toJSON k = isTypeKey k (A.String . pack . symbolVal . keyOf $ k)
data SomeTypeKey = forall a x . TK (TypeKeyOf a x)
instance FromJSON SomeTypeKey where
parseJSON (A.String s)
| s == "int" = return $ TK IntK
| s == "string" = return $ TK StringK
parseJSON _ = mzero
ここでも、Payload
タイプの変数が存在するように定量化されています。これは、あなたがこのタイプではあまり働かないことを意味するわけではありません(実際には、もっと多くのことができます)。
data Payload where
Payload :: a `TypeKeyOf` s -> a -> Payload
instance ToJSON Payload where
toJSON (Payload k a) =
object [ "type" .= k
, isTypeKey k $ "data" .= a
]
instance FromJSON Payload where
parseJSON (Object v) =
(v .: "type") >>= \(TK q) -> isTypeKey q (Payload q <$> v .: "data")
parseJSON _ = mzero
様々なポイントでisTypeKey
は様々なものが適切なクラスのインスタンスであることを証明するために使用される方法に注意してください。
あなたはショーのインスタンスを記述しようとした場合:
instance Show Payload where
show (Payload k a) = isTypeKey k $
"Payload " <> symbolVal (keyOf k) <> " " <> show a
あなたはNo instance for Show a ...
を取得します。あなたが「知っていればという
>decode "{\"type\": \"string\", \"data\": \"hello\"}" :: Maybe Payload
Just Payload string "hello"
>decode "{\"type\": \"int\", \"data\": 42}" :: Maybe Payload
Just Payload int 42
注:
type IsTypeKey a x = (ToJSON a, FromJSON a, KnownSymbol x, Show a)
そして今のタイプは本当に完全に解析によって決定されますが、それは単に存在量化されています。これは、IsTypeKey
に必要な制約を加えることで固定されています。実際のタイプのペイロードでは、タイプセーフな方法でその値を に引き出すことができます。実際にはがすべてタイプを知っているので、常に正確に何かを見つけることができます。
class HasTypeKey a (x :: Symbol) | x -> a where
typeKey :: TypeKeyOf a x
instance HasTypeKey Int "int" where typeKey = IntK
instance HasTypeKey String "string" where typeKey = StringK
typeKeyOf :: HasTypeKey a x => Proxy x -> TypeKeyOf a x
typeKeyOf _ = typeKey
sameKey :: TypeKeyOf a x -> TypeKeyOf a' x' -> Maybe ('(a, x) :~: '(a', x'))
sameKey IntK IntK = Just Refl
sameKey StringK StringK = Just Refl
sameKey _ _ = Nothing
extractPayload :: HasTypeKey a x => Proxy x -> Payload -> Maybe a
extractPayload t' (Payload t x) = fmap (\Refl -> x) $ sameKey t (typeKeyOf t')
「実際のタイプを指定しないで作業する」とは、コンパイラーがどのタイプであるかを把握したい場合、単純に不可能です。内部的に格納された型の表現を持っていても、それを復元することはできません。あなたが何をしていても、常に現実的に定量化されなければなりません。それでも、あなたのショーインスタンスは、 'Show a => Show(Message a)'なので、あなたはメッセージを表示するために 'Show a'の証明を作る義務があります。 'KnownSymbol'は' Message'に格納されています)。 – user2407038
@ user2407038、うん、良い点。それは私が不思議に思っていたものです。それで、一般的なままにするのではなく、デコードの正確なタイプを指定する必要があるようです。したがって、このアプローチは、 'type'フィールドを見ることなく、実際のタイプがわからないネットワーク上のメッセージをデコードするときには機能しません。 – Sal
このアプローチは機能します。話すためには、現存する定量化弾を噛むだけです。実在の定量化を扱うことは時には厄介ですが、ランタイム値から情報を入力する唯一の方法です。少し修正されたアプローチの私の答えを参照してください。 – user2407038