通用方法:判断一个值的类型是否属于一个类型类

3

我希望在这段代码中编写 isShowable 函数。

data MaybeShowable = forall a . Show a => Showable a | Opaque
f :: (Data d) => d -> String
f x = case isShowable x of
        Showable s -> show s
        Opaque -> "<<OPAQUE>>"
isShowable :: (Data d) => d -> MaybeShowable
isShowable = ???

通过使用Data实例,这是否可能实现?如果不行,最好的方式是什么?

注意:如果没有其他选项,我愿意接受仅适用于在定义isShowable的模块中可见的类型类实例的版本。


据我所知,在运行时无法提取特定值的类型类实例。在我找到的Data.TypeableData.DataAPI中,这不是其中的一部分。 - bheklilr
如果这是可能的,单独编译将会相当难以正确执行。我认为这是不可能的。 - chi
如果我愿意接受“类型类实例可通过导入到定义isShowable的模块中进行查看”,这会改变你的答案吗? @bheklilr @chi - tohava
在这种情况下,可能会有一些类型类的黑科技可以实现这一点,利用IncoherentInstances或相关扩展。尽管我不是那个领域的专家,无法给出明确的答案。 - chi
@chi - 我只是想指出,如果使用Template Haskell(就像C宏一样)始终强制将isShowable内联,则在我提到的限制下,它变得更加可用。这就是为什么我仍然对这个案例感兴趣的原因。 - tohava
2个回答

4

我不确定你的真实意图是什么,但看起来你想将Java习惯嵌入到Haskell中。

正如在其他SO问题中提到的那样,你所做的事情将变成反模式。

你已经添加了一个澄清:

如果我愿意接受“类型类实例可通过导入到定义isShowable的模块中的方式可见”。

为什么不将你的类型包装在中:

data MaybeShowable a where
  Showable :: forall b. Show b => b -> MaybeShowable b
  Opaque   :: forall b.           b -> MaybeShowable b

instance Show (MaybeShowable a) where
  show (Showable x) = show x
  show (Opaque x)   = "<<OPAQUE>>"

请将您的函数操作于 MaybeShowable a,而非普通的 a

但这仍然很丑陋。直接操作 Show a => aa 不是更容易吗?

另一种方法是尽早捕获 Show a 字典,即有数据类型:

data MaybeShowable a = Showable a String -- Or even Showable a (a -> String)
                     | Opaque a

instance Show (MaybeShowable a) where
  show (Showable x s) = s
  show (Opaque x)     = "<<OPAQUE>>"

wrapShow :: Show a => a -> MaybeShowable a
wrapShow x = Showable x (show x) -- Showable x show

wrapOpaque :: a -> MaybeShowable a
wrapOpaque = Opaque

这种方法的变体在例如QuickCheck的forAll中使用。那部分是Haskell98。在那里,show x被封闭到闭包中,可能会被执行或不执行。惰性是关键点!

1
你的意思是 wrapShow x = Showable x (show x),对吗? - chi

2

您可以使用模板哈斯凯尔语言查询可用的实例:

    module IsInstance where

    import Language.Haskell.TH; import Data.Typeable
    import Data.Generics; import Data.Monoid

    -- $(isInst ''Show) :: Typeable a => a -> Bool
    isInst :: Name -> ExpQ
    isInst className = do
        ClassI _ insts <- reify className
        ClassI _ typeableInsts <- reify ''Typeable
        let typeOfs = [ [| typeRep (Proxy :: Proxy $(return ty)) |]
                            | InstanceD _ (AppT _ ty) _ <- insts,
                              hasNoVarT ty,
                              or [ ty_ == ty | InstanceD _ (AppT _ ty_) _ <- typeableInsts ] ]
        [| \ val -> typeOf val `elem` $(listE typeOfs) |]

    hasNoVarT xs = getAll $ everything
        (<>)
        (mkQ mempty (\ x -> case x of
                      VarT {} -> All False
                      _ -> mempty))
        xs

$(isInst ''Show) (1 :: Int) 是正确的,但是不幸的是$(isInst ''Show) (1 :: Rational)是错误的。因为在这里使用==并不能说明实例Show (Ratio a)可以用于type Rational = Ratio Integer。所以一个完整的解决方案需要知道如何选择实例。


这个函数能在我原来问题中的 f 函数内使用吗?我有印象它只适用于单态输入类型。 - tohava
好的 $(isInst ''Show) :: Typeable a => a -> Bool,所以从技术上讲它是多态的。但是如果你得到一个True,那并不能让你调用show,所以它有点不足。我们可以生成一个类,其中包含一种方法,该方法可以确定要应用于类型的哪个GADT构造函数(@phadej解决方案的手动部分)。这在此处完成:https://gist.github.com/aavogt/3d9d2b468fb51ec5d3ca - aavogt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接