函数式编程公理
Posted
技术标签:
【中文标题】函数式编程公理【英文标题】:Functional programming axioms 【发布时间】:2017-10-03 16:05:49 【问题描述】:我正在学习使用 Clojure 进行函数式编程,并希望加深对函数式范式(不仅仅是 Clojure 的语法)的理论理解。
我正在寻找 axioms 或 formulas 每种功能技术(如 recursion、map、reduce、cons、first ans rest 的关系彼此,哪个是可派生/可组合的,哪个是一切背后的终极公理。
例如,我意识到map
只能使用recur
、first
、rest
和cons
函数来实现,当然映射函数本身也传递给map
。
在那之后,我也意识到map
也可以使用reduce
来实现,而reduce 也可以使用recur
、first
和rest
来实现。 filter
也可以用 reduce
来实现。
我觉得我开始专注于函数式编程,但仍然很难看出哪些是最终极的构建块,即哪些是抽象或的最小集合关键字 组成任意函数。对于地图示例,第二种方法使用较少的抽象来定位相同的目标。那么,有哪些功能范式的终极公理可以帮助我了解全局?
【问题讨论】:
在这种情况下,我建议您查看这本书:mitpress.mit.edu/books/little-schemer 这是对 FP 的精彩介绍,向您展示了如何仅使用基本数量的元素来制作事物。经典的SICP 也有很大帮助。两者都使用 lisp(确切地说是方案),因此很容易将您的理解转移到 clojure 函数式编程在很大程度上植根于拉姆达微积分,核心公理如下所示。 别忘了map
也可以换成for
【参考方案1】:
从 lambda(在 clojure 中称为 fn
),您可以派生任何其他内容。例如,让我们做一个经典的练习,用fn
推导cons
、first
和rest
:
(defn cons [x y]
(fn [f]
(f x y)))
(defn first [coll]
(coll (fn [x y] x)))
(defn rest [coll]
(coll (fn [x y] y)))
因此,如果您想要一组用于函数式编程的公理,那么只有一个:lambda 是终极公理。有关如何推导其他特征的详细信息,请参阅以下文章:
经典的Lambda the Ultimate论文。 Programming with Nothing,一种更新的方法。这使用了 Ruby 语法,但这并不重要,因为它使用的唯一语言特性是 lambda。 SICP 也有关于从 lambda 派生 car/cdr/cons 的部分,作为解释抽象障碍价值的一部分:实现并不重要,只要它满足您建立的合同。当然,如果您对一般编程基础感兴趣,那么 SICP 是一本不错的读物。从 cmets 看来,这个答案有很多困惑;没给没看过的人解释是我的错。
我们的想法不是重新实现 clojure 的所有内置 first/rest 功能,它们是适用于各种序列的高级多态事物。相反,我们实现了三个 cons/first/rest 函数,它们协同工作以允许您通过满足以下合约来构建集合:
(= x (first (cons x y)))
(= y (rest (cons x y)))
您可以仅使用 lambda 构建更复杂的东西,例如 clojure 的实际 first/rest,但您必须首先发明一个完整的类型系统,因此涉及更多。
这是一个示例 repl 会话,描述了本练习旨在演示的内容:
(defn cons [x y]
(fn [f]
(f x y)))
(defn first [coll]
(coll (fn [x y] x)))
(defn rest [coll]
(coll (fn [x y] y)))
user> (def integers (cons 0 (cons 1 (cons 2 (cons 3 nil)))))
#'user/integers
user> integers
#object[user$cons$fn__2108 0x3fb178bd "user$cons$fn__2108@3fb178bd"]
user> (first integers)
0
user> (first (rest (rest integers)))
2
【讨论】:
这看起来很有希望,但即使我阅读了 clojure 文档,我也不明白。那些书中是否解释了这个“经典例子”?集合如何用作函数?我在 clojure REPL 中尝试过这个并得到错误IllegalArgumentException Key must be integer
。
我也对这里发生的事情感到困惑。您正在使用函数为集合编制索引。
我的编辑改进了吗? first 和 rest 没有错误;相反,预期的用途显然不清楚。
新示例有助于阐明coll
必须使用您的cons
函数构造; coll
不代表通用 Clojure 集合。
@amalloy,太优雅了!【参考方案2】:
首先要了解列表在大多数函数式语言中是怎样的cons
tructed,这意味着为什么将列表视为first
和rest
如此有意义。这个递归定义是理解更改它们的递归机制的关键。
我第一次接触 map/filter/fold 等的方式实际上是通过 haskell,它有表达事物类型的好处。这对初学者来说很有意义,至少对我来说是这样。
例如,map
有签名(a -> b) -> [a] -> [b]
读作:如果你有一个函数接受类型a
并将其转换为类型b
,并且你给出类型a
的列表,那么 map 只会返回一个b
类型的列表。
您真正应该花时间理解的是fold
(left
和right
),其中reduce
是打字世界中的一个特例。一旦你觉得准备好了,我建议你回顾一下旧的A tutorial on the universality and
expressiveness of fold
我强烈鼓励您尝试并实现您提到的所有内容,您对这些基本构建块及其依赖项的理解将会大大提高。在recur
和reduce
等方面实现减少(和两个折叠),实现map
filter
take
等。
【讨论】:
很好的练习,但是foldr
在 Clojure 中的用处远不如在 Haskell 中有用,因为我们缺乏持久的惰性。尝试自己实现它,然后在大型集合上实际使用它。 or :: [Bool] -> Bool; or = foldr (||) False
因为短路而非常适合大型集合,但 Clojure 中的版本并不好。
绝对同意你@amalloy。我认为对于刚开始学习折叠的人来说,了解折叠是什么以及它们之间的区别是一个很好的练习。【参考方案3】:
例如,我认为您无法找到一组类似于概率论的简单公理。对于概率,只有 3 个基本公理:
对于每个事件 A,P[A] >= 0 P[“任何事件”] = 1 如果 A 和 B 互斥,则 P[ A 或 B] = P[A] + P[B]令人惊讶的是,概率和统计中的所有内容都可以从这三个基本假设中推导出来。
“函数式编程”的定义不是很好。事实上,几乎每本关于该主题的书都以这样的观察开始:如果您请 100 位“专家”来定义函数式编程,您将收到 100 个相互不兼容的答案。这句话只是部分开玩笑。
关于函数式编程,你真正可以说的唯一一点是它比传统或“非函数式”语言更强调函数。这实际上更像是函数式语言或函数式编程风格的“目标”,而不是是/否的观察。
函数式编程的目标始终如一:通过更简单和更可靠来节省成本。当然,自从计算开始以来,每种语言和技术都有相同的游戏计划。 FP 主要通过以下方式实现它:
减少可变变量的使用 增加使用函数而不是手动循环请注意,它说的是“减少”和“增加”,而不是“仅”和“从不”。决定这意味着什么是一个判断电话,答案会根据手头的问题和你问的人而改变。
请记住,问题和人员都会随着时间而变化。随着问题、人员、工具、硬件、价格等的变化,今天在成本、复杂性、效率、可维护性等之间进行“最佳”权衡的答案可能不会在一个月或一年内成为“最佳”答案随着时间的推移。
记住科学校长。它迫使您尝试事物(实验),而不仅仅是思考它们(理论)。所以你必须实际做一些事情并观察结果。
在软件中,这意味着以 2 种(或更多)方法解决问题,并比较每种方法的优缺点。
【讨论】:
以上是关于函数式编程公理的主要内容,如果未能解决你的问题,请参考以下文章