您的代码存在一些句法和语义错误。我将解释语法问题,然后进入您的解决方案的语义。
-
在 Haskell 中,函数、变量和类型变量的名称必须以 小写 字母开头;大写名称指的是类型、数据构造函数或模块。因此需要将IsInList重命名为isInList,并修正名称拼写错误。
-
定义上的where 子句后跟一个变量绑定块 (<em>pattern</em> = <em>expression</em>)。一个块可以用花括号分隔,项目用分号分隔({ … ; … ; … }),但更常见的是使用基于缩进的布局。因此,您需要缩进 where 块的内容。一个简单的规则是在 any 布局关键字之后放置一个换行符 + 缩进(带空格)——where、do、of 或 let——只要你有一个多行块。
-
函数的类型签名写成<em>name</em> :: <em>type</em>,但是你在isInList (x:xs) (y:ys) :: [a] -> [a] -> Bool的左侧写了一个看起来像pattern的东西。如果你想在这里包含一个类型签名,你应该写isInList :: [a] -> [a] -> Bool。但是,我将只删除签名,原因我将在下面解释。
通过这些修复,您的代码如下所示:
filterVars :: [a] -> [a] -> [a]
filterVars = filter isInList (y:ys)
where
isInList [] _ = []
isInList (x:xs) (y:ys)
| elem (x (y:ys)) = isInList (xs (y:ys))
| otherwise = x : isInList (xs (y:ys))
这是我对这段代码的阅读:
-
filterVars 等于使用两个参数调用filter,isInList 和y : ys。 y 和 ys 不在此范围内,因为您尚未将它们作为参数或模式变量添加到 filterVars 的定义中。
-
isInList 接受两个参数;如果第一个是空列表,则返回一个空列表。这不太对,因为filter 的类型为(a -> Bool) -> [a] -> [a],也就是说,它的第一个参数应该是一个函数,它测试过滤列表中的一个元素并返回一个Bool 来指示它是否应该包含在结果。
-
如果isInList 有两个非空列表,则后面跟着一个守卫来决定继续处理哪种情况。您正在尝试使用elem 函数来测试x 是否在列表y : ys 中作为这里的条件,但是您调用它的方式,您将y : ys 作为参数传递给x ,就好像它是一个函数一样,然后在其结果上调用elem。如果你想将两个参数传递给一个函数,你只需用空格分隔它们,并在必要时包含括号以进行分组,例如:elem x (y : ys)。当您可能打算使用isInList xs (y:ys) 时,您对isInList (xs (y:ys)) 的两次调用都有相同的问题。
在这一点上,我很清楚您混淆了两件事:使用filter 过滤列表,以及通过编写递归函数对其进行过滤。
如果你想用filter 来写这个,你应该从问题陈述开始:“一个函数,它接受两个列表,x 和 y 并输出 [s] 列表 x 但不在列表中的元素列表列出 y”。你想写一个函数,它接受两个参数,x 和y,并通过一个函数过滤x,给定x 的元素,如果该元素不在y 中,则返回true。
这可以从字面上理解:
filterVars :: [a] -> [a] -> [a]
-- Select the elements of ‘x’ not in ‘y’…
filterVars x y = filter notInY x
where
-- …where an element ‘e’ is not in ‘y’ if
-- it’s ‘not’ the case that ‘e’ is an ‘elem’ of ‘y’.
notInY e = not (elem e y)
但这有一个问题:这个函数说它可以获取 any 类型 a 的值列表,并使用 elem 测试它们在列表中的存在,但这不是不是真的:我们可以提供像Double -> String 这样的类型,根本无法比较!实际上,elem 仅限于在具有相等性的类型集合中的类型,Eq 类型类,因此我们必须在签名中添加一个Eq 约束以启用elem 的使用:
filterVars :: (Eq a) => [a] -> [a] -> [a]
现在,关于 where 块中的本地类型签名,Haskell 中的规则是,如果您使用类型变量编写多态类型签名(通常是单个字母,如 a、b、f、 m…) 那么这些变量对于该类型签名是 local 的。所以如果你写这个:
filterVars :: (Eq a) => [a] -> [a] -> [a]
filterVars x y = filter notInY x
where
notInY :: a -> Bool
notInY e = not (elem e y)
相当于这样:
filterVars :: (Eq a1) => [a1] -> [a1] -> [a1]
filterVars x y = filter notInY x
where
notInY :: a2 -> Bool
notInY e = not (elem e y)
notInY 签名中的a 与filterVars 签名中的a不同,因此这会导致类型错误。解决方法是去掉本地类型签名或者使用ScopedTypeVariables扩展来显式定义a的范围:
{-# LANGUAGE ScopedTypeVariables #-}
filterVars :: forall a. (Eq a) => [a] -> [a] -> [a]
filterVars x y = filter notInY x
where
notInY :: a -> Bool
notInY e = not (elem e y)
forall 为a 引入了显式作用域,因此此定义中类型签名中的任何a 均指代相同 a。 (显式 forall 就像许多命令式编程语言中用于定义泛型参数的 <T> 语法一样。)
就风格而言,您也可以用几种不同的方式编写相同的结构,并且您应该确保您理解这些:
filterVars x y = filter (notIn y) x
where
notIn list e = not (elem e list)
filterVars x y = filter (\ e -> not (elem e y)) x
filterVars x y = filter (`notElem` y) x
这是非常典型的 Haskell 代码,也是一个很好的简洁解决方案。但是既然你正在学习 Haskell,你应该尝试用显式递归来解决这个问题,有效地将 filter 的定义内联到你的代码中。为此,您确实希望像在 isInList 函数中尝试那样进行模式匹配。您可以从以下任一结构开始,使用 case 表达式或多个函数方程,无论您喜欢哪一种:
filterVars :: (Eq a) => [a] -> [a] -> [a]
filterVars x y = case x of
[] -> {- 1 -}
e : rest
| {- 2 -} -> {- 3 -}
| otherwise -> {- 4 -}
filterVars :: (Eq a) => [a] -> [a] -> [a]
filterVars [] y = {- 1 -}
filterVars (e : rest) y
| {- 2 -} = {- 3 -}
| otherwise = {- 4 -}
并填写以下详细信息,上面代码中用{- … -}cmets标记:
-
[a];当x 为空时,您会返回什么,即当您直接调用filterVars [] ["c", "b", "a"] 或作为递归的基本情况时会发生什么?
-
Bool;当x 非空时,包含一些元素e 和一些(可能为空的)余数rest,你用什么条件来测试e 是否是y 的元素? (例如,filterVars ["a", "d", "b"] ["c", "b", "a"],e 是 "a",rest 是 ["d", "b"]。)
-
[a];当e 是 y 的一个元素时,你会返回什么?您应该保留还是丢弃该元素?输入的rest 是做什么用的?
-
[a];当e 在y 中not 时,你会返回什么?那你输入的rest怎么办呢?
回想一下,您始终可以使用 等式推理 逐步执行 Haskell 代码,方法是替换 = 符号两侧的内容,因为 = 实际上意味着相等!例如,如果您的函数有一个案例f [] = [],那么每当您看到f [],您可以替换它为[];如果它有一个案例f (x : xs) = x + f xs,当你看到对f [1, 2, 3]的调用时,这与f (1 : [2, 3])相同,同样f (1 : (2 : (3 : []))),所以你可以匹配x = 1和xs = 2 : (3 : []) 并在右侧替换它们的值 x + f xs → 1 + f [2, 3]。