简体   繁体   English

展开存在量化的 GADT

[英]Unwrapping an existentially quantified GADT

I have a custom value type Value labeled by its type ValType :我有一个自定义值类型Value ,其类型为ValType

data ValType
  = Text
  | Bool

data Value (tag :: ValType) where
  T :: Text -> Value 'Text
  B :: Bool -> Value 'Bool

and I would like to define a function that unwraps an existentially quantified Value , that is it should have the following type signature:我想定义一个 function 来展开存在量化的Value ,即它应该具有以下类型签名:

data SomeValue = forall tag. SomeValue (Value tag)

unwrap :: SomeValue -> Maybe (Value tag)

I can define unwrap for 'Bool and 'Text separately, but how do I define a polymorphic unwrap ?我可以分别为'Bool'Text定义 unwrap ,但是如何定义多态unwrap

You really can't avoid a typeclass or equivalent here.你真的不能在这里避免一个类型类或等价物。 unwrap , as you've written its type, has no way to know which tag it's looking for, because types are erased. unwrap ,因为您已经编写了它的类型,所以无法知道它正在寻找哪个标签,因为类型已被删除。 An idiomatic approach uses the singleton pattern.惯用方法使用 singleton 模式。

data SValType v where
  SText :: SValType 'Text
  SBool :: SValType 'Bool

class KnownValType (v :: ValType) where
  knownValType :: SValType v
instance KnownValType 'Text where
  knownValType = SText
instance KnownValType 'Bool where
  knownValType = SBool

unwrap :: forall tag. KnownValType tag => SomeValue -> Maybe (Value tag)
unwrap (SomeValue v) = case knownValType @tag of
  SText
    | T _ <- v -> Just v
    | otherwise -> Nothing
  SBool
    | B _ <- v -> Just v
    | otherwise -> Nothing

Unlike the IsType class of your own answer, KnownValType lets you get type information as well as a value tag out of a pattern match.与您自己的答案的IsType class 不同, KnownValType允许您从模式匹配中获取类型信息以及值标记。 So you can use it much more generally for handling these types.因此,您可以更广泛地使用它来处理这些类型。

For cases where your typeOf is sufficient, we can write it with no trouble:对于您的typeOf足够的情况,我们可以轻松编写它:

 typeOf :: KnownValType a => Proxy a -> ValType
 typeOf (_ :: Proxy a) = case knownValType @a of
   SBool -> Bool
   SText -> Text

As yet another alternative, using Typeable and cast makes for a pretty concise solution.作为另一种选择,使用Typeablecast是一个非常简洁的解决方案。 You still have to carry around a dictionary, but you don't have to build it yourself:你仍然需要随身携带一本字典,但你不必自己构建它:

{-# LANGUAGE DataKinds, FlexibleInstances, GADTs,
    KindSignatures, StandaloneDeriving, OverloadedStrings #-}

import Data.Text (Text)
import Data.Typeable

data ValType
  = Text
  | Bool

data Value (tag :: ValType) where
  T :: Text -> Value 'Text
  B :: Bool -> Value 'Bool
deriving instance Show (Value 'Text)
deriving instance Show (Value 'Bool)

data SomeValue = forall tag. SomeValue (Value tag)

unwrap :: (Typeable tag) => SomeValue -> Maybe (Value tag)
unwrap (SomeValue (T t)) = cast (T t)
unwrap (SomeValue (B b)) = cast (B b)

main = do
  print (unwrap (SomeValue (T "foo")) :: Maybe (Value 'Text))
  print (unwrap (SomeValue (T "foo")) :: Maybe (Value 'Bool))

possible solution, defined a typeclass to reify types of kind ValType back to terms:可能的解决方案,定义了一个类型类以将ValType类型的类型具体化为术语:

class IsType a where
  typeOf :: Proxy a -> ValType

instance IsType 'Text where
  typeOf _ = Text

instance IsType 'Bool where
  typeOf _ = Bool

unwarp :: IsType tag => SomeValue -> Maybe (Value tag)
unwarp (SomeValue v) =
  case typeOf (Proxy @tag) of
    Bool ->
      case v of
        B _ -> Just v
        _ -> Nothing
    Text ->
      case v of
        T _ -> Just v
        _ -> Nothing

But I'll have to carry around that typeclass dictionary which is not very elegant.但是我必须随身携带那个不是很优雅的类型类字典。

Would this be acceptable?这可以接受吗?

data SomeValue = forall tag. (Typeable tag) => SomeValue (Value tag)

unwrap :: (Typeable tag) => SomeValue -> Maybe (Value tag)
unwrap (SomeValue t) = cast t

The "cast general type to Maybe specific type" pattern is pretty much what Typeable is for. “将一般类型转换为Maybe特定类型”模式几乎就是Typeable的用途。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM