如何告知GHC某类模式匹配不可能发生(例:列表永不为空)?
嘿,这个问题我太熟了!在Haskell里要告诉GHC某个模式匹配永远不会触发,正好适配你写的minf函数——毕竟你已经通过selectionSort的逻辑牢牢保证了传给minf的列表绝不会是空的对吧?下面给你三种实用的方法,从最优雅到最直接的都有:
1. 用NonEmpty类型(类型层面锁死非空,首推!)
Haskell的base库(GHC 8.0及以上自带)里的Data.List.NonEmpty模块提供了NonEmpty a类型,专门用来表示不可能为空的列表。用它来约束minf的输入类型,不仅能让GHC彻底打消“会不会传空列表”的疑虑,还能靠类型系统做静态检查——以后谁要是不小心改代码导致可能传空列表,编译器直接就会报错,根本等不到运行时崩溃。
首先导入模块,然后修改你的函数定义:
import Data.List.NonEmpty (NonEmpty(..), toList) selectionSort :: Ord a => [a] -> [a] selectionSort [] = [] selectionSort (x:xs) = minElem : selectionSort ys where (minElem, ys) = minf (x :| xs) -- 把普通非空列表转成NonEmpty类型 minf :: Ord a => NonEmpty a -> (a, [a]) minf (x :| []) = (x, []) -- 处理单元素非空列表 minf (x :| xs') = let (m, ms) = minf (head xs' :| tail xs') in if x <= m then (x, xs') else (m, x:ms)
这样改完,GHC不仅不会再提示模式匹配不全,代码的意图也更清晰——谁看了都知道minf只处理非空列表。
2. 用{-# COMPLETE #-}编译指示(不改类型,直接告诉编译器)
如果你不想改动现有类型,只想让GHC知道“这个函数的输入永远不会是空列表,不用瞎担心”,可以用GHC的COMPLETE编译指示。需要先开PatternSynonyms扩展,定义一个表示非空列表的模式同义词,再告诉编译器这个模式已经覆盖了所有可能的输入:
{-# LANGUAGE PatternSynonyms #-} -- 定义非空列表的模式同义词 pattern NonEmpty x xs = x:xs -- 告诉GHC:对于minf的输入类型[a],只有NonEmpty模式会出现,空列表永远不会来 {-# COMPLETE NonEmpty #-} minf :: Ord a => [a] -> (a, [a]) minf (NonEmpty x []) = (x, []) minf (NonEmpty x xs) = let (m, ms) = minf xs in if x <= m then (x, xs) else (m, x:ms)
这种方法不用改核心逻辑,但依赖编译器扩展,而且没有静态检查——万一以后代码逻辑变了真的传入空列表,还是会运行时崩溃,所以优先级比NonEmpty低一些。
3. 加error/undefined分支(简单直接,最偷懒的办法)
如果只是想快速消掉编译器警告,最简单的方式就是给minf加一个空列表的模式匹配分支,用error抛出明确的错误信息,或者用undefined:
minf :: Ord a => [a] -> (a, [a]) minf [] = error "minf: 不可能传入空列表!因为selectionSort只会传非空列表过来" minf [x] = (x, []) minf (x:xs) = let (m ,ms) = minf xs in if x <= m then (x, xs) else (m, x:ms)
这种方法最直接,但不够优雅——毕竟我们明明可以靠类型系统避免这种情况,而不是等到运行时才报错。不过在你的代码场景里,这个分支永远不会被触发,所以用来消警告完全没问题。
总结一下,首推用NonEmpty类型,安全又清晰;其次是COMPLETEpragma;最偷懒的就是加error分支。
内容的提问来源于stack exchange,提问作者duggi

