函数式编程公理

Posted

技术标签:

【中文标题】函数式编程公理【英文标题】:Functional programming axioms 【发布时间】:2017-10-03 16:05:49 【问题描述】:

我正在学习使用 Clojure 进行函数式编程,并希望加深对函数式范式(不仅仅是 Clojure 的语法)的理论理解。

我正在寻找 axiomsformulas 每种功能技术(如 recursion、map、reduce、cons、first ans rest 的关系彼此,哪个是可派生/可组合的,哪个是一切背后的终极公理。

例如,我意识到map只能使用recurfirstrestcons函数来实现,当然映射函数本身也传递给map

在那之后,我也意识到map 也可以使用reduce 来实现,而reduce 也可以使用recurfirstrest 来实现。 filter 也可以用 reduce 来实现。

我觉得我开始专注于函数式编程,但仍然很难看出哪些是最终极的构建块,即哪些是抽象的最小集合关键字 组成任意函数。对于地图示例,第二种方法使用较少的抽象来定位相同的目标。那么,有哪些功能范式的终极公理可以帮助我了解全局?

【问题讨论】:

在这种情况下,我建议您查看这本书:mitpress.mit.edu/books/little-schemer 这是对 FP 的精彩介绍,向您展示了如何仅使用基本数量的元素来制作事物。经典的SICP 也有很大帮助。两者都使用 lisp(确切地说是方案),因此很容易将您的理解转移到 clojure 函数式编程在很大程度上植根于拉姆达微积分,核心公理如下所示。 别忘了map也可以换成for 【参考方案1】:

从 lambda(在 clojure 中称为 fn),您可以派生任何其他内容。例如,让我们做一个经典的练习,用fn 推导consfirstrest

(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】:

首先要了解列表在大多数函数式语言中是怎样的constructed,这意味着为什么将列表视为firstrest 如此有意义。这个递归定义是理解更改它们的递归机制的关键。

我第一次接触 map/filter/fold 等的方式实际上是通过 haskell,它有表达事物类型的好处。这对初学者来说很有意义,至少对我来说是这样。

例如,map 有签名(a -> b) -> [a] -> [b] 读作:如果你有一个函数接受类型a 并将其转换为类型b,并且你给出类型a 的列表,那么 map 只会返回一个b 类型的列表。

您真正应该花时间理解的是foldleftright),其中reduce 是打字世界中的一个特例。一旦你觉得准备好了,我建议你回顾一下旧的A tutorial on the universality and expressiveness of fold

我强烈鼓励您尝试并实现您提到的所有内容,您对这些基本构建块及其依赖项的理解将会大大提高。在recurreduce 等方面实现减少(和两个折叠),实现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 种(或更多)方法解决问题,并比较每种方法的优缺点。

【讨论】:

以上是关于函数式编程公理的主要内容,如果未能解决你的问题,请参考以下文章

函数式编程--为什么要学习函数式编程?

函数式编程和非函数式编程

第36期函数式编程和响应式编程资料汇总

RxJS 与 函数式编程 - 函数式编程

函数式编程

函数式编程