簡體   English   中英

我如何'說服'GHC我已經排除了某個案例?

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

我有以下非空列表( 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 _ _) 有沒有其他方法可以限制類型,所以GHC知道b嚴格指代List 'NotEmpty類型的東西?

一個顯而易見的方法是使用unsafeCoerce但這會破壞練習的重點。


  

您可以做的一件事是傳遞空列表的替代方案:

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

將此模式擴展到foldrfoldr1留給讀者練習。

你已經“定義” 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

每當您想要同一類型的多組構造函數時,請查看模式同義詞。 我們也可以轉換為您的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

給予

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