具有地图功能的Haskell函数组合

时间:2018-10-11 03:07:25

标签: haskell equality proof map-function function-composition

我正在阅读Richard Bird的“使用Haskell进行功能性思考”一书,其中有一段我无法理解他在哪里证明了filter方法的性质。他证明的是:

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 . f等于map 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]

谁能解释一下我在这里想念的东西吗?

2 个答案:

答案 0 :(得分:6)

您测试了

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

这确实是错误的。该属性实际上是

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

LHS关联的地方

test p . f = (test p) . f

请记住,函数应用程序比任何用户可定义的运算符都更紧密地绑定,就像它的infixl 10一样。彼此相邻的两个标识符始终是前缀函数应用程序的一部分。 (对于模式来说除外:f xs@ys zs表示f (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)的意思是“乘以3,确保结果小于15。” map (*3) . test ((<15) . (*3))的意思是“确保输入的三次小于15,如果是,则返回三次。”

答案 1 :(得分:4)

HTNW's answer涵盖了您的测试用例以及如何使用test的定义来证明方程式。我要说的是,仍然存在一个潜在的问题:我们应该从哪顶帽子拉出等式-我们为什么还要考虑它成真的可能性?为了回答这个问题,让我们首先看一下等式:

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

换句话说,它表示使用某些f函数修改值,然后给定适当的谓词p,对其应用test p与使用后修改值相同test(通过将谓词与f进行组合来对谓词进行适当修改,以使其适合未修改值的类型)。

接下来,让我们考虑test的类型:

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

这里的关键是,此类型的函数必须对a的任何选择都起作用。如果可以是任何东西,那么在实现这种类型的函数(例如test)时,我们对此一无所知。这严重限制了该函数的功能:尤其是结果列表中的元素(如果有)必须全部与提供的类型a相同(我们如何在不知不觉中将其更改为其他值)谓词的类型?),并且谓词必须被忽略或应用于相同的值(我们还要将其应用于什么?)。考虑到这一点,现在方程式说的很自然:我们在f之前还是之后用test更改值都没有关系,因为test不会更改值

使这种推理严格的一种方法是通过自由定理。由于参数多态性,一个类型的自由定理可确保始终保持该类型的任何可能值,并且除了类型之外,您无需任何其他操作即可确定该定理。碰巧forall a. (a -> Bool) -> a -> [a]的自由定理就是test p . f = map f . test (p . f)。由于我不能在这些简短的文章中对此主题做出公正的解释,因此这里有一些关于自由定理的参考文献: