为什么我的Haskell代码调用myLookUp时报空列表错误?
问题排查结果
核心错误原因
两次报错的根本原因完全一致:myLookUp递归到最后一步时,给myLast传入了空列表:
你删掉myLast [] = error "Empty list"分支后出现非穷尽模式报错,也是因为myLast []没有匹配的分支,和之前主动抛出的空列表错误是同一个触发源。
以你给出的测试调用myLookUp [("strength", ["ignorance"]), ("bla", ["blabla", "blabla"])] "aa"为例,执行流程拆解如下:
- 第一次匹配分支
((k, vs):ds),此时ds = [("bla", ["blabla", "blabla"])]为非空,调用myLast ds正常执行。检查发现"aa"既不在最后一个元组的列表中,也不在当前vs中,触发递归调用myLookUp ds "aa" - 第二次进入
myLookUp,匹配((k, vs):ds)时,当前输入列表只有1个元素,因此ds = []。此时第一个守卫会执行myLast ds也就是myLast [],直接触发错误。
你单独测试myLast时传入的都是非空列表,因此不会触发问题。
修复方案
方案1:提前计算全局最后一个元组的列表(推荐)
你当前的逻辑是每次递归都检查目标值是否存在于整个字典的最后一个元组的列表中,这个判断完全可以在递归开始前执行一次即可,既可以避免重复遍历列表导致时间复杂度升到O(n²),也能规避空列表调用问题:
myLookUp :: [(String, [String])] -> String -> String myLookUp [] _ = [] -- 用@语法获取整个字典的引用,提前提取最后一个元组的列表 myLookUp dict@(_:_) val = let lastVals = snd $ myLast dict in go dict lastVals val where go [] _ _ = [] go ((k, vs):ds) lastVals val | val `elem` lastVals = myCensor val | val `elem` vs = k | otherwise = go ds lastVals val
方案2:给守卫加非空判断
如果你的实际需求是仅在ds非空时才检查最后一个元素的列表,可以直接给第一个守卫加空列表判断:
myLookUp :: [(String, [String])] -> String -> String myLookUp [] val = [] myLookUp ((k, vs):ds) val | not (null ds) && val `elem` snd (myLast ds) = myCensor val | val `elem` vs = k | otherwise = myLookUp ds val
额外注意事项
- 你的函数注释标注返回
Maybe key,但实际签名返回的是String,用空列表作为查找失败的返回值,和注释描述不符 - 代码中引用的
myCensor函数未给出实现,需要确保该函数存在且返回值类型为String
内容的提问来源于stack exchange,提问作者Mampenda
相关产品推荐
相关产品推荐

