我是Haskell的新手。我想编写一个代码,检查String中是否有元音。 我提出了这个解决方案:
n :: Int
n = 0
vowel :: String -> Bool
vowel a
| check n = a !! n
| check == 'a' || 'e' || 'i' || 'o' || 'u' || 'y' = True
| check /= 'a' || 'e' || 'i' || 'o' || 'u' || 'y' = check(n+1)
| otherwise = False
所以基本上,如果有元音,我想用递归来逐字检查。 我刚收到错误消息:
Couldn't match expected type `Bool' with actual type `Char'
* In the second argument of `(||)', namely 'y'
In the second argument of `(||)', namely 'u' || 'y'
In the second argument of `(||)', namely 'o' || 'u' || 'y'
|
14 | | check /= 'a' || 'e' || 'i' || 'o' || 'u' || 'y' = check(n+1)
|
问题出在哪里?
答案 0 :(得分:11)
这段代码有很多问题:
n = 0
,并以某种方式预期这是check
中的默认值;事实并非如此; check n = a !! n
,然后使用check == ...
,变量只有一种类型; =
不 作业,它是声明。声明后,您无法再更改该值; check == 'a' || 'e' || 'i' || 'o' || 'u' || 'y'
,但是||
比==
更少,所以你写了(check == 'a') || 'e' || 'i' || 'o' || 'u' || 'y'
,因为一个字符'e'
不是布尔值(和在Haskell中没有真实性),你不能使用'e'
作为||
的操作数; n
最终会变得那么大,你会得到索引超出范围的异常; !!
本身并没有错,但它被认为效率低下( O(n))并且它不是一个完整的函数,所以不安全 any
让我们构建一个满足需求的函数。如果我正确理解了要求,你需要检查元音中是否至少有一个元素。
字符c
如果是'a'
,'e'
,'i'
,'o'
,'u'
或{{1},则为元音}}。所以我们可以写一张支票:
'y'
但这是相当不优雅的。由于isVowel :: Char -> Bool
isVowel c = c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'y'
是String
的列表,我们可以使用Char
函数,因此我们可以写:
elem :: Eq a => a -> [a]
现在我们只需要检查字符串isVowel :: Char -> Bool
isVowel c = elem c "aeiouy"
中是否有任何字符c
是元音,所以我们可以写:
s
我们可以进一步改善这个问题。编写vowel :: String -> Bool
vowel s = any (\c -> isVowel c) s
形式的lambda表达式是没用的。相反,我们可以简单地写\x -> f x
。所以我们可以写:
f
我们可以在vowel :: String -> Bool
vowel s = any isVowel s
上应用相同的技巧:我们可以在s
函数的头部和正文中删除它:
vowel
最后,我们可以使用vowel :: String -> Bool
vowel = any isVowel
来设置isVowel
函数无点:
flip
这导致:
isVowel :: Char -> Bool
isVowel = flip elem "aeiouy"
然后我们可以测试它。例如:
isVowel :: Char -> Bool
isVowel = flip elem "aeiouy"
vowel :: String -> Bool
vowel = any isVowel
Prelude> vowel "foobar"
True
Prelude> vowel "qx"
False
Prelude> vowel "bl"
False
Prelude> vowel "bla"
True
函数是一个更高阶函数:它将一个函数作为输入,这使得它很难理解。我们可以为any :: (a -> Bool) -> [a] -> Bool
函数编写我们自己的specialed any
函数。我们在这里使用列表,通常在进行列表处理时,我们必须考虑至少两种模式:空列表vowel
和非空列表[]
。由于(x:xs)
相当简单,这就足够了。
如果我们处理空列表,我们知道字符串中没有元音,所以我们可以写:
any
如果列表非空vowel [] = False
,则它有 head (第一个元素)(x:xs)
和 tail (其余元素) )x
。如果第一个元素是元音,或者任何其余元素是元音,则字符串包含元音。所以我们可以写:
xs
如果我们将它们放在一起(与vowel (x:xs) = isVowel x || vowel xs
函数一起),我们得到:
isVowel
答案 1 :(得分:3)
您的代码需要更多关注。然而,这个特定错误非常简单。运算符||
需要两个Bool
值。 'a'
,'b'
等值的类型为Char
,但是,您不能在此处使用||
。
您需要的是使用||
的一系列布尔值。理论上,第一个值check == 'a'
是Bool
,因为运算符==
可以采用两个值(某些类型,包括Char
)并返回Bool
}。那么你想要的是创造一系列的比较以获得许多布尔。因此,您必须使用==
运算符将check
与每个元音进行比较:
| check == 'a' || check == 'e' || check == 'i' || check == 'o' || check == 'u' || check == 'y' = True
这是开始编码时常见的误解。用英语,可以说
如果
check
等于'a'
,或'e'
,或'i'
......
我们尝试将其直接翻译成代码。然而,在Haskell中,"或" operator(||
)无法弄清楚这意味着什么。你已经明确了每一个条件:
如果
check
等于'a'
,或check
等于'e'
,或check
等于'i'
......
解决此问题后,您的代码是否有效?不,还有其他问题。例如,check
未在任何地方定义。尽管如此,每次解决一个问题并转移到下一个问题,最终你会得到你想要的东西:)
答案 2 :(得分:1)
您已定义n = 0
。我怀疑你打算用它作为一种在每个递归步骤中都增加的运行变量。这在Haskell中是不可能的 - 如果您在顶层定义n = 0
,那么n
总是为0
。
要拥有这样一个“运行变量”,你需要使它成为递归循环函数的参数。您好像尝试使用check
及其n
参数进行类似的操作(这是一个完全不同的变量,阴影全局n
- 阴影可以容易导致混淆,避免它)。这种本地函数的标准名称是go
,它将像
vowel :: String -> Bool
vowel a = go 0
where go n = case a !! n of
... -> False
... -> True
... -> ... go (n+1) ...
使用list-indexing循环遍历列表的所有元素。这是笨拙和不安全的(当你在列表之外索引时给出一个模糊的错误)†;做这样的事情的首选方法是使用the standard folding operations(见下文),或者通过模式匹配直接在列表上编写递归,而不是整数索引变量。因此,我们可以再次删除n
参数,并在列表中递归:
vowel :: String -> Bool
vowel a = go a
where go (x:xs) = case x of
... -> False
... -> True
... -> ... go xs ...
请注意,这会在列表末尾自动失败,因为模式x:xs
不再匹配。简单明了,只需添加一个特殊条款:
where go (x:xs) = case x of
... -> False
... -> True
... -> ... go xs ...
go [] = ...
实际上,此时,go
只是vocal
的本地预定义,因此您不妨将递归拉到顶层:< / p>
vowel :: String -> Bool
vowel [] = ...
vowel (x:xs) = ... vowel xs ...
您已尝试在保护列表中声明check
。这不受支持 - 见上文,where
应该用于本地定义,或者let
‡。
您已尝试在||
的单个链中编写多个相等选项。这是不可能的,因为每次比较都采用(在这种情况下)数字并给出Bool
结果,因此您不能只链接它们。你可以做的是写出x == 'a' || x == 'e' || ...
,但这显然很尴尬。一个更简洁的选项,suggested by Willem,就是写x `elem` "aeiouy"
代替:这将是正确的事情。
如果您还没有遇到过这些反引号``
:它们会使用elem
之类的双参数函数并将其置于中缀模式。这类似于+
中的中缀n + 1
。
关于中缀的好处是你可以创建一个section:定义一个带字符的函数并将它与每个元音进行比较,你可以编写
isVowel c = elem c "aeiouy"
但你也可以写
isVowel = (`elem` "aeiouy")
请注意,我已通过eta reduction完全删除了变量。这称为point-free style。
这就派上用场了:没有必要将isVowel
定义为应用程序的命名函数 - 无论如何只能将它用于一个目的,即与字符串中的所有字符进行比较。将操作应用于容器的所有元素并将结果¶组合的最常用方法是foldr
。您可以像
vowel = foldr ((||) . (`elem` "aeiouy")) False
这有点神秘;幸运的是,logical-or和fold的组合有一个标准名称:any
。有了它,你可以像
vowel = any (`elem` "aeiouy")
† 索引到列表中的效率也非常低:!!
需要遍历所有元素,直到所请求的元素。当 O ( n 时,这会使您尝试复杂的 O ( n 2 ) em>)就足够了。
‡ 顺便提一下, 可以在单个警卫中定义某些东西,但只能滥用pattern guard feature。 < / p>
¶ 要在容器中再次收集结果,您可以使用更简单的fmap
。
答案 3 :(得分:0)
使用高阶函数的简单解决方案,适用于可能会发现此问题的中间Haskeller:
checkVowels :: String -> String
checkVowels = any (`elem` "aeiouyw")