【问题标题】:Can I parameterise the empty constraint type?我可以参数化空约束类型吗?
【发布时间】:2015-06-24 02:49:22
【问题描述】:

我有一个队列类,它允许实例定义它对元素的约束。例如,优先级队列要求其元素是可排序的:

{-# LANGUAGE MultiParamTypeClasses, ConstraintKinds, FunctionalDependencies #-}

class Queue q c | q -> c where
    empty :: q a
    qpop :: c a => q a -> Maybe (a, q a)
    qpush :: c a => a -> q a -> q a

data PriorityQueue a = ...

instance Queue PriorityQueue Ord where
    ...

这很有魅力:在PriorityQueue 的实例声明中,我可以使用Ord 的成员对队列元素进行操作,例如(>)


我一直在尝试定义一个对其元素没有要求的队列:

newtype LIFO a = LIFO [a]

instance Queue LIFO () where
    empty = LIFO []
    qpop (LIFO []) = Nothing
    qpop (LIFO (x:xs)) = Just (x, LIFO xs)
    qpush x (LIFO xs) = LIFO $ x:xs

这失败了,来自 GHC 的错误消息如下:

The second argument of `Queue' should have kind `* -> Constraint',
  but `()' has kind `*'
In the instance declaration for `Queue LIFO ()'

此错误消息对我来说很有意义。 Eq 接受类型参数(我们通常写成 Eq a => ...),而 () 没有参数 - 这是一个普通的旧类型不匹配。


我在编写忽略其第二个参数的类型函数时遇到了难题,这将允许我编写instance Queue LIFO (Const ())

{-# LANGUAGE TypeFamilies, KindSignatures, PolyKinds #-}

type family Const a b :: k -> k2 -> k
type instance Const a b = a

我发现类型族和类型多态的这种交互非常漂亮,所以当它不起作用时我很失望(我真的以为它会!):

Expecting two more arguments to `a'
The first argument of `Const' should have kind `*',
  but `a' has kind `k0 -> k1 -> k0'
In the type `a'
In the type instance declaration for `Const'

我觉得最后一个例子有点像语法错误一样愚蠢(我是类型族的新手)。我怎样才能写出不对其论点施加任何限制的Constraint

【问题讨论】:

  • 尝试定义 type family Const (a :: k1) (b :: k2) :: k1 然后 Const (() :: Constraint) 有你想要的那种,但它仍然卡住。
  • @J.Abrahamson 是的,它似乎卡在instance 声明中:Illegal type synonym family application in instance。为什么我最初在Const 的尝试失败了?
  • 您可能也有兴趣使用TypeFamilies 进行此操作。 skybluetrades.net/blog/posts/2015/03/08/…
  • @snak 感谢您的链接。这看起来像是将我的FunctionalDepenencies 版本直接翻译成TypeFamilies。 (这是非常合理的,因为类型族概括了函数依赖。)我个人更喜欢这里的函数依赖,但这纯粹是一个品味问题。

标签: haskell typeclass type-constraints type-families


【解决方案1】:

这应该可行:

class NoConstraint a where
instance NoConstraint a where

instance Queue LIFO NoConstraint where
  ...

上面定义了一个所有类型都满足的约束。因此,c a 其中c = NoConstraint 的义务始终可以被解除。 此外,由于该类中没有成员,因此它的运行时间成本应该为零(或几乎为零)。

您尝试使用的“约束”() 不是 GHC 设置的空约束,而是单元类型 () :: *。这会导致Const () :: k2 -> *,从而触发 kind 错误。

如果您不想使用自定义类,您可以尝试例如Const (Eq ())Const (Num Int),它们具有正确的类型 k2 -> Constraint。不过我不推荐这样做,因为我发现它的可读性不如使用自定义类。

(这需要启用一些扩展,正如 Benjamin Hodgson 在下面的评论中指出的那样。)

【讨论】:

  • 谢谢!实际上,我在您回答之前就独立地得出了该解决方案!我认为值得指出的是,这需要FlexibleInstances,并且在打开PolyKindsKindSignatures 时效果最好(所以你可以写class NoConstraint (a :: k) - 否则GHC 将假定a :: *)。
  • 我在这里看到的问题是NoConstraint 约束将“感染”所有使用队列的东西。使用Const (NoConstraint ()) 可以解决这个问题吗?
  • @dfeuer 如果您一般使用队列,则约束看起来像(Queue q c, c a) => q a,我认为这并不太繁重
  • @dfeuer 如果一般使用Queue,那么只会生成c a,不涉及NoConstraint a。相反,如果您使用特定的Queue LIFO 实例,则将生成Constraint a,然后立即释放。因此,据我所知,这绝不应该“感染”代码中的任何签名。
猜你喜欢
  • 2011-12-11
  • 1970-01-01
  • 1970-01-01
  • 2016-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多