我最近刚刚开始学习Haskell,更具体地说是关于函数组合,部分函数,映射,过滤器和剖分的主题。在其中一个练习中,我被要求通过使用函数组合来修改twoFilters
函数。
我已经在.
上阅读了几个wiki,但我很难让它正常工作。据我了解,它的工作原理是按字母顺序执行函数b . a
并返回结果。换句话说,x = foo a
然后是foo b of x
。然而,在使用两个过滤器函数应用了几个“变体/可能性”之后,由于错误,我无法进行编译。
greaterThanOne :: Int -> Bool
greaterThanOne = (>1)
lessThanTen :: Int -> Bool
lessThanTen = (<10)
twoFilters :: [Int] -> [Int]
twoFilters xs= filter lessThanTen (filter greaterThanOne xs)
这两个是我最有信心的失败尝试;
twoFilters xs = filter (lessThanTen . greaterThanOne xs)
twoFilters xs = filter (lessThanTen xs . greaterThanOne xs)
在推理的哪个地方,我出错了?
答案 0 :(得分:3)
你有信心的尝试是你的逻辑中的一个简单的失败:点运算符的工作原理如下:
(f.g)(x) = f(g(x))
因此,尝试计算5的示例给出:
lessThanThen(greaterThanOne(5)) = lessThanTen(True) -- that can't be right, can it???
你想要的是一个lambda和&amp;&amp;:
filter (\x-> (lessThanThen x) && greaterThanOne(x))
或者,您可以使用两个过滤器:
filter lessThanTen . filter greaterThanOne $
答案 1 :(得分:3)
进入应用函数的精彩世界:
import Control.Applicative
greaterThanOne = (>1)
lessThanTen = (<10)
twoFilters = filter ((&&) <$> greaterThanOne <*> lessThanTen)
twoFilters [1,2,3,4,5,6,7,8,9,10]
-- [2,3,4,5,6,7,8,9]
答案 2 :(得分:2)
你不能像这样编写这两个函数。 f . g
的作用类似于数学中的合成,即相当于f(g(x))
。这意味着外部函数必须采用内部函数返回的类型的参数,在您的情况下,外部函数必须是Bool -> Bool
。
您可以使用合成运算符编写twoFilters
,如下所示:
twoFilters = (filter lessThanTen) . (filter greaterThanOne)
答案 3 :(得分:1)
(.)
需要一个函数,该函数接受一个参数并返回一个值,但是您在其中传递Bool
值:
lessThanTen . greaterThanOne xs
这是错误的。
下面:
lessThanTen xs . greaterThanOne xs
您尝试撰写两个Bool
值,但您应该编写两个返回Bool
值的函数。
答案 4 :(得分:1)
功能应用程序具有最高优先级的一个问题。因此lessThanTen . greaterThanOne xs
尝试使用lessThanTen
的结果组合greaterThanOne xs
(这不起作用,该函数适用于整数,而不适用于其列表)。同样地,lessThanTen xs. greaterThanOne xs
试图组合这些函数调用的结果(假设它们首先是有意义的),而不是它们自己的函数。
另一个问题是对.
的误解 - (f . g) x
等同于f (g x)
,即第一个函数的结果是第二个函数的参数。因此g
的类型必须为(a -> b)
,f
的类型必须为(b -> c)
(两个b
都是相同的类型变量!)。您希望将两个函数应用于同一参数并将结果与&&
结合使用。据我所知,目前还没有现有功能(至少Hoogle没有为(a -> Bool) -> (a -> Bool) -> a -> Bool
找到任何东西)。你必须自己做:
both f g x = f x && g x
或者,您只需坚持过滤两次(由于懒惰的评估,这并不像听起来那么糟糕) - filter (>1) $ filter (<10) xs
。
答案 5 :(得分:1)
据我了解,它的工作原理是按字母顺序执行函数
b . a
并返回结果。换句话说,x = foo a
然后是foo b of x
这可以用Haskell编写为
let x = foo a in
foo b x
(foo
来自哪里?)但是正确的
(b . a) x = let y = a x in
b y
或者,更短:
(b . a) x = b (a x)
现在,filter lessThanTen (filter greaterThanOne xs)
与此定义的右侧具有相似的形状,如果您还记得可以将其写为(filter lessThanTen) ((filter greaterThanOne) xs)
:
((filter lessThanTen) . (filter greaterThanOne)) xs
大概你真正想要的是filter ??? xs
,但这应该足以继续了。
答案 6 :(得分:1)
你几乎是对的。我发现使用.
开始学习函数合成的最简单方法是首先使用$
。
所以你有一个清单
twoFilters xs = xs
您希望按greaterThanOne
twoFilters xs = filter greaterThanOne $ xs
您还希望按lessThanTen
twoFilters xs = filter lessThanTen $ filter greaterThanOne $ xs
现在从左向右移动,将所有$
替换为.
,除了上一个$
twoFilters xs = filter lessThanTen . filter greaterThanOne $ xs
您现在可以使用括号而不是$
:
twoFilters xs = (filter lessThanTen . filter greaterThanOne) xs
或者只是定义函数pointfree:
twoFilters = filter lessThanTen . filter greaterThanOne
我认为括号内的版本是最重要的。它表明您将两个部分应用的函数filter lessThanTen
和filter greaterThanOne
融合到一个超级过滤函数中,使用.
和然后将列表应用于它。您需要将它们括起来,因为.
通过空格绑定比函数应用程序更紧密(空间可以被认为是$
的超高固定性版本)。请记住,当您使用.
时,您将两个函数融合在一起形成一个超级函数。
检查.
(.) :: (b -> c) -> (a -> b) -> a -> c
您提供的功能必须与非常特殊的类型签名“对齐”(它们必须兼容融合)。但老实说,关键是要学会识别函数应用程序(带空格)何时绑定比你打算更紧密,并弄乱你想要编写的函数的类型签名。无论如何,这就是我的意思。