我具有以下非空列表(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
例如,很容易定义仅对非空列表起作用的“安全头”功能:
eHead :: NEList a -> a
eHead (Cons a _) = a
最后一个同样容易,但有一点复杂:
eLast :: NEList a -> a
eLast (Cons a Nil) = a
eLast (Cons _ b@(Cons _ _)) = eLast b
该模式之所以需要这样的原因是为了使GHC确信b
的类型确实是List 'NotEmpty
,而不是未知的存在类型。由于以下原因,以下代码失败:(Couldn't match type ‘e’ with ‘'NotEmpty’ ...
)
eLast :: NEList a -> a
eLast (Cons a Nil) = a
eLast (Cons _ b) = eLast b
我完全知道为什么。我想知道的是,我是否可以避免每次都要写b@(Cons _ _)
?有什么我可以限制类型的 other 方法,以便GHC知道b
严格指的是List 'NotEmpty
类型的东西?
一种明显的方法是使用unsafeCoerce
,但这违反了练习的重点。
为了重现性,这是我的序言:
{-# OPTIONS_GHC -Wall -Werror #-} -- To prevent missed cases.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
import Data.Kind
答案 0 :(得分:4)
您可以做的一件事是传递空列表的替代方法:
lastDef :: a -> List e a -> a
lastDef a Nil = a
lastDef _ (Cons a b) = lastDef a b
然后在顶层将其包装一次。
last :: NEList a -> a
last (Cons a b) = lastDef a b
将此模式扩展到foldr
和foldr1
留给读者练习。
答案 1 :(得分:3)
您已经用NEList
定义base
的相同方式“定义”了NonEmpty
(我以一种宽松的方式说):作为单个元素附加到可能为空的头部列表。
data NonEmpty a = a :| [a]
NonEmpty
的另一种表示形式将单个元素放在末尾。
data NonEmpty a = Single a | Multiple a (NonEmpty a)
毫无疑问,此演示文稿使eLast
变得容易:
eLast (Single x) = x
eLast (Multiple _ xs) = eLast xs
每当要在同一类型上使用多组构造函数时,请使用模式化同义词。除了基本的NonEmpty
,我们还可以翻译为您的List
。
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
给予
eLast :: NEList a -> a
eLast (Single x) = x
eLast (Multiple _ xs) = eLast xs