如何在Haskell中的约束中应用通配符?

时间:2015-08-07 21:55:23

标签: haskell

这听起来微不足道,但我无法找到我应该做的事情。

以下是我的类型定义:

data CDeq q a = Shallow (q a)
              | Deep{ hq   ⦂ q a
                    , susp ⦂ CDeq q (q a)
                    , tq   ⦂ q a }

我希望它有一个Show的实例。

由于GHC在这里不允许deriving,我只是试着自己写一个:

instance (Show a, Show ????) => Show (CDeq q a) where
    ....

但我被卡住了。

我不知道如何在Haskell中表示for all type v, (q v) can be shown

我不能简单地执行以下操作:

instance (Show a, Show (q a)) => Show (CDeq q a) where
    ....

由于要显示CDeq q (q a)Show (q (q a))是必需的,因此需要Show (q (q (q a))),然后依次开启。

所以我想知道是否有一种语法可以表达我在那里所说的含义?

我曾经认为forall可以解决这个问题,但它不起作用:

instance (Show a, forall v. Show (q v)) => Show (CDeq q a)

2 个答案:

答案 0 :(得分:10)

Prelude.Extras中有一个班级Show1代表“适用于所有类型v(q v)可以显示”。

class Show1 f where
  showsPrec1 :: Show a => Int -> f a -> ShowS
  default showsPrec1 :: (Show (f a), Show a) => Int -> f a -> ShowS
  showsPrec1 = showsPrec
  ...

您可以使用它为CDeq q a编写一个show实例。

instance (Show a, Show1 q) => Show (CDeq q a) where
    ....

您在show上使用showsPrecq x的位置,而不是show1showsPrec1

如果您使用这些,则还应提供CDeq q的实例。

instance (Show1 q) => Show1 (CDeq q) where
    showsPrec1 = showsPrec

答案 1 :(得分:3)

@ Cirdec已经接受的答案对于这个用例是一个非常实用的答案。我想写一些关于更多通用技术的内容(特别是评论中提到的Data.Constraint.Forall,因为几乎适用于这个用例,但并不完全,并且有&#39 ; constraints 工作的另一件事,但我想尝试解释一下为什么这不是直接可能的以及Show1ForallLifting做什么来解决这个问题(他们是每次不同的权衡)。所以它有点长,道歉。

通过标准的Haskell机制,你可以拥有这样一个"通配符"约束。原因是Show从类型中创建约束,对于可以应用它的每种类型,可能有不同的实例(具有不同的方法定义)。

当您的代码需要Show (CDeq q a)个实例并找到instance (Show a, Show ????) => Show (CDeq q a)时,这意味着它现在还需要找到Show aShow ????的实例。

Show a很容易;已经选择a作为Int之类的具体类型,并且可以对该类型使用Show实例(如果范围内没有{1}}则会出错。或者你的代码是a中的多态函数;在这种情况下,您编写的函数必须有Show a约束,因此编译器将依赖调用者选择某些特定a并传入{ {1}}实例定义。

但是通配符约束Show a是不同的。我们不是在谈论具体类型,因此解决问题的途径不会起作用。我们甚至没有谈论多态约束,因为调用者会选择一个类型变量(在这种情况下,我们可以解决为调用者选择单个实例字典的问题)。

您需要的是能够展示Show ????q aq (q a)等等。但每个人都可以拥有自己的实例!这些类型在运行时消失了,因此编译器甚至无法尝试对所有这些实例进行舍入(或要求调用者传入无限数量的实例)并发出在q (q (q a)之间切换的代码它叫。它需要发出仅调用show的一个版本的代码;来自其能够选择的特定实例的特定实例,或者由调用者传递给函数的特定实例。

解决此问题的一种方法是使用替代类型类show。这是"重载单位"在Show1类型构造函数上。为* -> *类型设置Show1实例是不可能的(它们的形状不正确),但单个Int实例适用于表单Show1 q的所有类型,因此您不需要无限数量的实例来支持q aq aq (q a)等等

但是在某些类型变量中还存在 多态的q (q (q a))个实例。例如,列表实例是Show;知道此实例存在(并且没有任何重叠的实例),我们知道我们将为Show a => Show [a][a][[a]]等使用相同的实例如果我们能够以某种方式编写一个需要像这样的多态实例的约束。

没有直接方式来说我们想要一个多态实例 - 约束语言只允许我们询问特定类型的实例,或者调用者选择的特定类型。但是@Cirdec在评论中建议的约束包中的[[[a]]]模块在​​内部使用了巧妙的技巧来提供一些方法。 1 这里& #39;这是一个看起来如何的例子:

Data.Forall

约束{-# LANGUAGE FlexibleContexts , ScopedTypeVariables , TypeApplications , TypeOperators #-} module Foo where import Data.Constraint ( (:-), (\\) ) import Data.Constraint.Forall (ForallF, instF) data Nested q a = Stop | Deeper a (Nested q (q a)) data None a = None instance Show (None a) where show None = "None" instance (Show a, ForallF Show q) => Show (Nested q a) where show Stop = "Stop" show (Deeper a r) = show a ++ " " ++ show r \\ (instF @Show @q @a) 代表ForallF Show q 2 但它不是直接约束,所以我们不能导出forall a. Show (q a)用这个;我们需要手动编写一个实例,以便我们可以进行一些按摩。

Show约束使我们能够访问类型为ForallF的{​​{1}}。 instF是约束包的一种类型;类型forall p f a. ForallF p f :- p (f a)的值表示当约束:-保持约束时c :- d也成立(或者就实例字典而言:它包含c的字典&## 39; s在d的字典上进行参数化。因此d证明,当我们c时,我们可以获得ForallF p f :- p (f a)。键入应用程序语法是一种不那么详细的方法来确定我们使用ForallF p f的类型,我们希望p (f a)的左侧与instF绑定到:-知道我们从实例约束。这意味着右手边将根据我们的需要为我们提供ForallF Show qShow (q a)运算符只在左侧显示一个表达式,在右侧显示\\,基本上为我们连接c :- dc个实例;表达式将通过访问d的字典进行评估,但整体表达式只需要d

以下是一个使用示例:

c

乌拉!但为什么我使用λ Deeper 'a' (Deeper None (Deeper None Stop)) 'a' None None Stop it :: Nested None Char ?当我们尝试使用嵌套列表时会发生什么?

None

讨厌鬼。什么地方出了错?好吧,我们列表的多态λ :t Deeper [] (Deeper [] Stop) Deeper [] (Deeper [] Stop) :: Nested [] [t] λ Deeper [] (Deeper [] Stop) <interactive>:65:1: error: • No instance for (Show (Data.Constraint.Forall.Skolem (Data.Constraint.Forall.ComposeC Show []))) arising from a use of ‘print’ • In a stmt of an interactive GHCi command: print it 实例实际上是Show。实例头是多态的,因此应用于所有类型的表单Show a => Show [a]。但它还需要[a]所拥有的额外约束,因此它不是真正的多态。基本上发生的事情是Show a中的内部未导出的东西没有Data.Constraint的实例(它不能有任何技术工作的实例),所以我们得到了错误以上。这实际上是一件好事; Show的词典包含[a]的嵌套字典,因此获取我们知道的实例的技巧是多态的,然后不安全将其转换为正确的类型将不适用于此处。 a仅用于查找完全多态的实例,完全没有限制。

还有这个约束包必须提供的东西! ForallF为我们提供了一个班级Data.Constraint.Lifting,代表Lifting p f拥有&#34;时所持有的#34; p (f a)。约束p a&#34;提升通过&#34;类型构造函数p。这实际上完全你需要的概念,因为你可以递归地应用它来任意嵌套f的任意深度。

q

此类{-# LANGUAGE FlexibleContexts , ScopedTypeVariables , TypeApplications , TypeOperators #-} module Foo where import Data.Constraint ( (:-), (\\) ) import Data.Constraint.Lifting ( Lifting (lifting) ) data Nested q a = Stop | Deeper a (Nested q (q a)) instance (Show a, Lifting Show q) => Show (Nested q a) where show Stop = "Stop" show (Deeper a r) = show a ++ " " ++ show r \\ (lifting @Show @q @a) 类的lifting方法基本上正在执行Lifting以前的操作。 instF,所以当我们lifting :: Lifting p f => p a :- p (f a)并且Lifting Show q时,我们可以使用Show a\\(使用正确的类型)来获取lifting {1}}字典我们需要以递归方式调用Show (q a)

现在我们可以显示应用于列表类型的show

Nested

λ Deeper [] (Deeper [[True]] Stop) [] [[True]] Stop it :: Nested [] [Bool] 在前奏中确实有很多预定义的实例,但您可能需要编写自己的实例。幸运的是,这通常是写作的问题:

Data.Constraint.Lifting

实例解析器为您完成所有实际工作,前提是您的类型确实 允许该类通过&#34;提升。它

1 我对该模块中的代码的理解并非100%完成(并且尽可能安全地详细介绍了完整的细节),但基本上该技术是应用于类隐藏未导出的东西并捕获字典。由于没有第三方实例可以实际引用我们未导出的东西,实例可以解决的唯一方法是它是否实际上是多态的并且可以用于任何事情。因此,捕获的字典只需instance Lifting SomeClass MyType where lifting = Sub Dict 即可应用于您喜欢的任何类型。

2 还有一些unsafeCoerced的其他变体用于表示不同的&#34;形状&#34;约束中的多态性我相信你不能制作一个适合所有人的版本,因为你不必提及你变形多变的变量,这意味着你无法实际使用所应用的约束,你必须有一些东西,将类作为参数以及所有非多态参数,并以特定方式将它们一起应用。