DataKinds和类型类实例

时间:2015-09-04 23:54:12

标签: haskell typeclass data-kinds

以下示例是我现实生活中的问题的简化版本。它似乎在某种程度上类似于Retrieving information from DataKinds constrained existential types,但我无法得到我所寻求的答案。

假设我们有一个有限的,提升的DataKind K,类型为AB,以及多边形Proxy数据类型,可以生成类型类型的术语*

{-# LANGUAGE DataKinds, PolyKinds, GADTs, FlexibleInstances, FlexibleContexts #-}

data K = A | B

data Proxy :: k -> * where Proxy :: Proxy k

现在我想写Show - Proxy aa K种类instance Show (Proxy A) where show Proxy = "A" instance Show (Proxy B) where show Proxy = "B" 的实例,正好是两个:

Show

但是要使用K - 实例,我必须明确提供上下文,即使该类型仅限于test :: Show (Proxy a) => Proxy (a :: K) -> String test p = show p

Show

我的目标是摆脱类型类约束。这似乎并不重要,但在我的实际应用中,这具有重大意义。

我还可以定义一个但更通用的instance Show (Proxy (a :: K)) where show p = "?" - 实例,如下所示:

A

这实际上允许我放弃约束,但新问题是区分Btest两种类型。

那么,有没有办法吃我的蛋糕呢?也就是说,不必在show的类型中提供类型类约束(类型注释很好),并且仍然有两个不同的A实现(例如通过某种方式区分类型)?

实际上,如果我可以简单地将相应类型(B"A")与其特定值相关联,那么删除整个类型类也是可以的(此处:"B"enctype="multipart/form-data")在我只有类型信息的上下文中。不过我不知道怎么做。

我非常感谢任何提供的见解。

3 个答案:

答案 0 :(得分:6)

不,这是不可能的。在您只有类型信息"的环境中,在运行时,您确实拥有 no 信息。类型信息被删除。所以即使是封闭的类型,原则上可以显示给定的类型,你总是可以提出一个字典,你仍然需要类约束。类约束确保在编译时,当GHC知道类型时,它可以选择要传递的适当实例。在运行时,输入它的信息会丢失,并且没有机会这样做。写一个"一个尺寸适合所有"实例确实有效,因为那时确切的类型对于选择无关紧要。

我不知道全貌,但是可以通过明确地将类字典或字符串本身与您传递的值捆绑在一起来解决这个问题......

答案 1 :(得分:5)

您可以添加另一个类。

class Kish (k :: K) where
  toK :: proxy k -> K

instance Kish A where
  toK _ = A

instance Kish B where
  toK _ = B

instance Kish k => Show (Proxy k) where
  showsPrec n _ = case toK (Proxy :: Proxy k) of
    A -> ...
    B -> ...

现在你仍然会遇到一个上下文,但它是一个更普遍的上下文,对其他事情也很有用。

如果事实证明您需要经常区分代理,那么您应该切换到GADT,您可以根据需要进行检查,而不是使用代理。

答案 2 :(得分:2)

知道:

  1. a属于K
  2. 唯一类型K AB
  3. Show (Proxy A)持有
  4. Show (Proxy B)持有
  5. 足以证明Show (Proxy a)成立。但是类型类不仅仅是我们需要证明与我们的类型一起使用的命题,它还提供了实现。要实际使用show (x :: Proxy a),我们不仅需要证明Show (Proxy a) 的实现存在,我们还需要知道它是哪一个。

    Haskell类型变量(没有约束)是参数化的:a无法完全多态,也能检查aA和{B提供不同的行为{1}}。你想要的“不同行为”就像你可能没有实际参数一样“接近参数”,因为当你知道每种类型都有一个时,它只是为每种类型选择不同的实例。但它仍然违反了test :: forall (a :: K). Proxy a -> String的含义。

    类型类约束允许您的代码在受约束的类型变量中是非参数的,因为您可以使用类型类从类型到实现的映射来根据调用的类型切换代码的​​行为方式。所以test :: forall (a :: K). Show (Proxy a) => Proxy a -> String有效。 (就实际实现而言,选择类型a的同一终极调用者还为从您的函数生成的代码提供Show (Proxy a)实例。

    您可以使用instance Show (Proxy (a :: K))的想法;现在,在a :: K类型中 参数化的函数仍然可以使用show (x :: Proxy a),因为有一个实例适用于所有a :: K。但实例本身遇到了同样的问题;实例中show的实现在a中是参数化的,因此无论如何都无法检查它以便根据类型返回不同的字符串。

    你可以使用类似于dfeuer的答案,其中Kish利用约束类型变量的非参数性来基本上允许你在运行时检查类型;实例字典传递以满足Kish a约束基本上运行时记录,其中为变量a选择了哪种类型(以回旋方式)。推进这个想法可以让你一路走到Typeable。但是你仍然需要某种约束来使你的代码在a中非参数化。

    您还可以使用明确表示选择AB的运行时表示的类型(而不是Proxy,这是运行时的空值,仅提供选择的编译时表示),如:

    {-# LANGUAGE DataKinds, GADTs, KindSignatures, StandaloneDeriving #-}
    
    data K = A | B
    
    data KProxy (a :: K)
      where KA :: KProxy A
            KB :: KProxy B
    
    deriving instance Show (KProxy a)
    
    test :: KProxy a -> String
    test = show
    

    (请注意,我不仅可以实现Show (Kproxy a),我实际上可以派生它,尽管它确实需要独立派生)

    这是使用GADT KProxy允许testa中是非参数的,基本上与来自dfeuer的答案的Kish约束做同样的工作但避免需要为类型签名添加额外的约束。在这篇文章的早期版本中,我声明test能够执行此操作,同时在a中保持“仅仅”参数,这是不正确的。

    当然,要传递代理,您必须实际编写KAKB。在你必须编写Proxy :: Proxy A来实际选择类型的情况下(这通常是代理的情况,因为你通常只使用它们来修复一个模糊不清的类型)。但无论如何,如果你与调用的其余部分不一致,编译器会抓住你,但是你不能只编写一个像Proxy这样的符号并让编译器推断出正确的含义。您可以使用类型类

    来解决
    class KProxyable (a :: K)
      where kproxy :: KProxy a
    
    instance KProxyable A
      where kproxy = KA
    
    instance KProxyable B
      where kproxy = KB
    

    然后,您可以使用KA代替Proxy :: Proxy Akproxy,而不是让编译器推断出裸Proxy的类型。愚蠢的例子时间:

    foo :: KProxy a -> KProxy a -> String
    foo kx ky = show kx ++ " " ++ show ky
    

    GHCI:

    λ foo KA kproxy
    "KA KA"
    

    注意我实际上并不需要在任何地方都有KProxyable约束;我在 类型已知的位置使用kproxy。但仍然必须“从顶部进入”(正如满足Show (Proxy a)约束的实例字典一样);在[{1}}类型中无法使用参数函数自行提供相关的a :: K

    因为它是构造函数和使其工作的类型之间的对应关系,所以我不相信你可以像运行时空的KProxy a那样以这种方式创建泛型代理。 TemplateHaskell当然可以为你生成这样的代理类型;我认为 singletons 的概念在这里是一般的想法,所以https://hackage.haskell.org/package/singletons可能提供了你需要的东西,但我不太熟悉如何实际使用那个包。