简体   繁体   English

我如何'说服'GHC我已经排除了某个案例?

[英]How can I ‘convince’ GHC that I've excluded a certain case?

I have the following toy implementation of a non-empty list ( NEList ) datatype: 我有以下非空列表( NEList )数据类型的玩具实现:

-- A type to describe whether or not a list is empty.
data Emptiness :: Type where
  Empty    :: Emptiness
  NotEmpty :: Emptiness

-- The list itself. Note the existential type in `Cons'.
data List :: Emptiness -> Type -> Type where
  Nil :: List 'Empty a
  Cons :: a -> List e a -> List 'NotEmpty a

type EList a = List 'Empty a
type NEList a = List 'NotEmpty a

For example it's very easy to define a 'safe head' function that only operates on non-empty lists: 例如,定义一个仅在非空列表上运行的“安全头”功能非常容易:

eHead :: NEList a -> a
eHead (Cons a _) = a

The last is similarly easy, but has a little complication: 最后一样容易,但有一点复杂性:

eLast :: NEList a -> a
eLast (Cons a Nil) = a
eLast (Cons _ b@(Cons _ _)) = eLast b

The reason the pattern needs to be like this is to convince GHC that the type of b is indeed List 'NotEmpty , instead of an unknown existential type. 模式需要这样的原因是为了说服GHC b的类型确实是List 'NotEmpty ,而不是未知的存在类型。 The following code fails for that reason: ( Couldn't match type 'e' with ''NotEmpty' ... ) 以下代码因此失败:( Couldn't match type 'e' with ''NotEmpty' ...

eLast :: NEList a -> a
eLast (Cons a Nil) = a
eLast (Cons _ b) = eLast b

I'm fully aware why this is happening. 我完全清楚为什么会这样。 What I'd like to know is, can I avoid having to write b@(Cons _ _) every time? 我想知道的是,我是否可以避免每次都写b@(Cons _ _) Is there some other way I can restrict the type, so that GHC knows that b is referring strictly to something of type List 'NotEmpty ? 有没有其他方法可以限制类型,所以GHC知道b严格指代List 'NotEmpty类型的东西?

One obvious way is to use unsafeCoerce but this defeats the point of the exercise. 一个显而易见的方法是使用unsafeCoerce但这会破坏练习的重点。


For the sake of reproducibility, here is my preamble: 为了重现性,这是我的序言:

 {-# OPTIONS_GHC -Wall -Werror #-} -- To prevent missed cases. {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} import Data.Kind 

One thing you can do is pass around an alternative for empty lists: 您可以做的一件事是传递空列表的替代方案:

lastDef :: a -> List e a -> a
lastDef a Nil = a
lastDef _ (Cons a b) = lastDef a b

Then wrap it up once at the top level. 然后在顶层将其包裹一次。

last :: NEList a -> a
last (Cons a b) = lastDef a b

Extending this pattern to foldr and foldr1 is left as an exercise for the reader. 将此模式扩展到foldrfoldr1留给读者练习。

You've "defined" NEList (I say that in a loose way) the same way base defines NonEmpty : as a single element tacked onto the head of a potentially empty list. 你已经“定义” NEList (我以松散的方式说) base定义NonEmpty方式相同:将一个元素添加到潜在空列表的头部。

data NonEmpty a = a :| [a]

Another presentation of NonEmpty instead places that single element at the end. NonEmpty另一个演示文稿将单个元素放在最后。

data NonEmpty a = Single a | Multiple a (NonEmpty a)

This presentation, no surprise, makes eLast easy: 这个演示,毫不奇怪,使eLast变得简单:

eLast (Single x) = x
eLast (Multiple _ xs) = eLast xs

Whenever you want multiple sets of constructors on the same type, look to pattern synonyms. 每当您想要同一类型的多组构造函数时,请查看模式同义词。 Instead of the base NonEmpty , we can also translate to your List . 我们也可以转换为您的List ,而不是基本的NonEmpty

pattern Single :: forall e a. () => e ~ 'NotEmpty => a -> List e a
pattern Single x = Cons x Nil
pattern Multiple :: forall e a. () => e ~ 'NotEmpty => a -> List 'NotEmpty a -> List e a
pattern Multiple x xs <- Cons x xs@(Cons _ _)
  where Multiple x xs = Cons x xs
-- my dormant bidirectional pattern synonyms GHC proposal would allow just
-- pattern Multiple x (Cons y xs) = Cons x (Cons y xs)
-- but current pattern synonyms are a little stupid, so Multiple is ugly
{-# COMPLETE Nil, Single, Multiple :: List #-}
-- Single and Multiple are actually patterns on List e a, not List NotEmpty a
-- Therefore the COMPLETE must include Nil, or else we'd show that all
-- Lists are nonempty (\case { Single _ -> Refl; Multiple _ _ -> Refl })
-- They are, however, still complete on List NotEmpty a
-- GHC will "infer" this by "trying" the Nil constructor and deeming it impossible

Giving 给予

eLast :: NEList a -> a
eLast (Single x) = x
eLast (Multiple _ xs) = eLast xs

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

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