Haskell函数组合与Map函数

Haskell Function Composition with Map Function

提问人:flopoe 提问时间:10/11/2018 最后编辑:Will Nessflopoe 更新时间:11/23/2018 访问量:610

问:

我正在阅读理查德·伯德(Richard Bird)的《用Haskell进行功能思考》(Thinking Functionally with Haskell)一书,其中有一节我不明白他在哪里证明了过滤器方法的特性。他要证明的是:

filter p . map f = map f . filter (p . f)

在本书的前面,他将过滤器定义为:

filter p = concat . map (test p)
test p x = if p x then [x] else []

这就是他如何证明第一个方程:

    filter p . map f
= {second definition of filter} -- He's referring to the definition I gave above
    concat . map (test p) . map f
= {functor property of map}
    concat . map (test p . f)
= {since test p . f = map f . test (p . f)}
    concat . map (map f . test (p . f))
= {functor property of map}
    concat . map (map f) . map (test (p . f))
= {naturality of concat}
    map f . concat . map (test (p . f))
= {second definition of filter}
    map f . filter (p . f)

我无法理解的是,如何等于.test p . fmap f . test (p . f)

这就是我尝试测试它的方式:

test :: (a -> Bool) -> a -> [a]
test p x = if p x then [x] else []

test ((<15) . (3*)) 4 -- test p .f, returns [4]
(map (3*) . test((<15) . (3*))) 4 -- map f . test (p . f), returns [12]

谁能解释一下我在这里错过了什么?

哈斯克尔 等式 证明 map-function-composition)

评论


答:

6赞 HTNW 10/11/2018 #1

你测试过

test (p . f) = map f . test (p . f)

这确实是错误的。该物业实际上是

test p . f = map f . test (p . f)

其中 LHS 关联为

test p . f = (test p) . f

请记住,函数应用程序比任何用户可定义的运算符都更紧密地绑定,就像 .两个彼此相邻的标识符始终是前缀函数应用程序的一部分。(除了 as-patterns: means .) 的情况。infixl 10f xs@ys zsf (xs@ys) zs

要证明属性:

    test p . f
={definition of (.)}
    \x -> test p (f x)
={definition of test}
    \x -> if p (f x) then [f x] else []
={definition of map, multiple times}
    \x -> if p (f x) then map f [x] else map f []
={float map f out of cases}
    \x -> map f (if p (f x) then [x] else [])
={definition of (.)}
    \x -> map f (if (p . f) x then [x] else [])
={definition of test}
    \x -> map f (test (p . f) x)
={definition of (.)}
    map f . test (p . f)

调整您的示例意味着“乘以 ,确保结果小于 ”。 表示“确保输入的三倍小于 ,如果是,则返回三倍的输入。test (<15) . (*3)315map (*3) . test ((<15) . (*3))15

4赞 duplode 10/11/2018 #2

HTNW 的答案涵盖了您的测试用例以及如何使用 的定义来证明方程。不过,我想说的是,仍然有一个潜在的问题:我们应该从哪顶帽子上得出这个等式——我们为什么要考虑它是真的可能性?为了回答这个问题,让我们先看一下这个等式:test

test p . f = map f . test (p . f)

换句话说,它说使用某个函数修改一个值,然后,给定一些合适的谓词,应用在它上面与使用后修改值是一样的(通过用 组合谓词来适当地修改谓词,使其适合未修改值的类型)。fptest ptestf

接下来,我们来考虑一下:test

-- I'm adding the implicit forall for the sake of emphasis.
forall a. (a -> Bool) -> a -> [a]

这里的关键是,具有此类型的函数必须适用于任何选择。如果它可以是任何东西,那么在实现这种类型的函数时,我们事先对它一无所知,例如 .这严重限制了这样一个函数可以做什么:特别是,结果列表的元素(如果有的话)必须与提供的类型值相同(我们怎么能在事先不知道其类型的情况下将其更改为其他内容?),并且必须忽略谓词或将其应用于相同的值(我们还会将其应用于什么?)。考虑到这一点,等式现在所说的感觉很自然:我们是用 before 还是 之后更改值并不重要,因为不会自行更改值。atestaftesttest

使这种推理严谨的一种方法是通过自由定理。由于参数多态性,一个类型的自由定理保证始终适用于该类型的任何可能值,并且除了类型之外,您不需要任何其他东西来计算它。碰巧 的自由定理是 。由于我无法在这些简短的文字中公正地对待这个主题,这里有一些关于自由定理的参考资料:forall a. (a -> Bool) -> a -> [a]test p . f = map f . test (p . f)

评论

0赞 Will Ness 10/22/2018
有人应该写一本书/博客,“The Annotated SO Haskell”,里面有像这样的精选条目。SO 本身的噪音水平太高。您知道在哪里/是否可以在 StackOverflow 部分(和 Reddithaskell.org/news 看到过去的条目吗?谁/什么/在哪里维护这个列表?这将是非常有价值的。
1赞 duplode 10/22/2018
@WillNess我相信 haskell.org/news 列表只是这个标签的原始问题提要(加上代码审查和软件工程网站上的对应物)。如果我们要播种某种精选列表,那么一个可能的起点可能是这里用户最喜欢的问题。这是SEDE玩具的草图,用于聚合它(尽管梳理标签常客的最爱也可能有所帮助)。