我正在阅读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]
谁能解释一下我在这里想念的东西吗?
答案 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)
。由于我不能在这些简短的文章中对此主题做出公正的解释,因此这里有一些关于自由定理的参考文献:
Parametricity: Money for Nothing and Theorems for Free是一个很好的阐述,如果您想更深入地了解它,可以指向主要的主要资源。
What Does fmap Preserve?并不是完全关于自由定理,而是(希望)以一种可访问的方式展示了一些相关主题。
相关堆栈溢出帖子:amalloy's answer to Polymorphic reasoning; Type signatures that never make sense。
lambdabot可以为您生成自由定理。您可以将其用作命令行工具,也可以通过在#haskell Freenode IRC通道上运行的漫游器使用。