数据库和函数式编程不一致吗?

我已经做了一段时间的 Web 开发人员,最近开始学习一些函数式编程。和其他人一样,我在将这些概念应用到我的专业工作中时遇到了一些重大困难。对我来说,主要的原因是我看到 FP 的目标之间的冲突,保持无状态似乎很不相符的事实,大多数网络开发工作,我已经做了严重的数据库绑定,这是非常以数据为中心。

使我在面向对象方面成为一个更高效的开发人员的一件事是发现了像 MyGeneration d00dads for 这样的对象关系映射器。Net,Class: : DBI 表示 perl,ActiveRecord 表示 ruby,等等。这样我就可以一整天都不用编写 insert 和 select 语句,而是专注于将数据轻松地作为对象处理。当然,当需要 SQL 查询的能力时,我仍然可以编写 SQL 查询,但除此之外,它在幕后得到了很好的抽象。

现在,转向函数式编程,对于许多 FP Web 框架,比如 Links,似乎需要编写大量的样板 sql 代码,就像在 这个例子中一样。Weblock 似乎更好一些,但它似乎使用了某种面向对象的模型来处理数据,并且仍然需要像在 这个例子中那样为数据库中的每个表手动编写代码。我假设您使用一些代码生成来编写这些映射函数,但是这看起来绝对不像 lisp。

(注意,我并没有非常仔细地研究 Weblock 或者 Links,我可能只是误解了它们是如何使用的)。

因此,问题是,对于 Web 应用程序的数据库访问部分(我认为是相当大的部分) ,或者其他需要与 sql 数据库接口的开发,我们似乎被迫走下列路径之一:

  1. 不要使用函数式编程
  2. 以一种恼人的、非抽象的方式访问数据,包括手动编写大量 SQL 或类似 SQL 的代码
  3. 强迫我们的函数式语言进入伪 OOP 范式,从而消除了真正函数式编程的一些优雅和稳定性。

显然,这些选择似乎都不理想。找到了规避这些问题的方法吗?真的有什么问题吗?

注意: 我个人最熟悉 FP 前端的 LISP,所以如果您想给出任何示例并了解多种 FP 语言,LISP 可能是首选语言

PS: 有关 Web 开发其他方面的特定问题,请参阅 这个问题

30469 次浏览

完全没有。有一种类型的数据库被称为“功能数据库”,其中 失忆症可能是最容易访问的例子。基本原则是函数式编程是声明性的,因此可以进行优化。您可以使用 清单理解在持久性集合上实现连接,查询优化器可以自动计算出如何实现磁盘访问。

Mnesia 是用 二郎编写的,并且至少有一个 Web 框架(Erlyweb)可用于该平台。Erlang 在本质上与无共享线程模型并行,因此在某些方面,它适合可伸缩架构。

我不认为 fp 语言的无状态特性是连接数据库的问题。Lisp 是一种非纯函数式编程语言,因此它在处理状态时应该没有任何问题。像 Haskell 这样的纯函数式编程语言有处理输入和输出的方法,这些方法可以应用于使用数据库。

从您的问题来看,您的主要问题似乎在于找到一种好的方法,将从数据库返回的基于记录的数据抽象为 lisp-y (lisp-ish?)而不必编写大量的 SQL 代码。这似乎更像是工具/库的问题,而不是语言范例的问题。如果您想要做纯 FP,那么 lisp 可能不是适合您的语言。Common lisp 似乎更多的是集成来自 oo、 fp 和其他范例的好想法,而不是纯粹的 fp。如果希望采用纯 FP 路线,也许应该使用 Erlang 或 Haskell。

我确实认为口齿不清中的“伪 -oo”思想也有其优点。你也许想试试。如果它们不符合您想要的处理数据的方式,您可以尝试在 Weblock 上创建一个层,允许您按照您想要的方式处理数据。这可能比你自己写所有的东西要容易。

免责声明: 我不是 Lisp 专家。我主要对编程语言感兴趣,并且一直在使用 Lisp/CLOS、 Scheme、 Erlang、 Python 和一些 Ruby。在日常编程生活中,我仍然被迫使用 C # 。

首先,我不会说 CLOS (Common Lisp 对象系统)是“伪 OO”。这是头等舱。

其次,我认为你应该使用适合你需要的模式。

您不能无状态地存储数据,而函数是数据流,并不真正需要状态。

如果你有几个混杂的需求,混合你的范例。不要限制自己只使用工具箱的右下角。

从数据库人员的角度来看,我发现前端开发人员过于努力地寻找使数据库适合他们的模型的方法,而不是考虑使用数据库的最有效的方法,这些方法不是面向对象的或功能性的,而是关系的和使用集合论的。我已经看到这通常会导致代码性能很差。此外,它还创建了难以进行性能调优的代码。

在考虑数据库访问时,有三个主要考虑因素——数据完整性(为什么所有业务规则都应该在数据库级而不是通过用户界面实施)、性能和安全性。SQL 是为了比任何前端语言更有效地管理前两个注意事项而编写的。因为它是专门为此设计的。数据库的任务与用户界面的任务大不相同。在管理任务时最有效的代码类型在概念上是不同的,这有什么好奇怪的吗?

数据库中保存的信息对公司的生存至关重要。难怪企业在生死攸关的时候不愿意尝试新方法。很多企业甚至不愿意升级到现有数据库的新版本。因此,在数据库设计中存在固有的保守性。而且是故意这么做的。

我不会尝试编写 T-SQL 或使用数据库设计概念来创建您的用户界面,为什么要尝试使用您的界面语言和设计概念访问我的数据库?因为您认为 SQL 不够新奇(或不够新颖) ?还是你觉得不舒服?仅仅因为某些事情不符合你感觉最舒服的模式,并不意味着它是坏的或错误的。这意味着它是不同的,可能出于合理的原因而不同。您可以为不同的任务使用不同的工具。

我和 Haskell 在一起最舒服。最著名的 Haskell Web 框架(类似于 Rails 和 Django)被称为 Yesod。它似乎有一个非常酷,类型安全,多后端 ORM。看看他们书中的 坚持篇

你应该看看本 · 莫斯利和彼得 · 马克斯的论文《走出沥青坑》 ,这里有: 《走出柏油坑》(2006年2月6日)

这是一本现代经典著作,详细介绍了一个叫做函数关系编程的编程范型/系统。虽然与数据库没有直接关系,但它讨论了如何将与外部世界(例如数据库)的交互与系统的功能核心隔离开来。

本文还讨论了如何实现一个系统,在这个系统中,应用程序的内部状态是通过一个关系代数定义和修改的,这显然与关系数据库有关。

本文将不会给出如何集成数据库和函数式编程的确切答案,但是它将帮助您设计一个系统来最小化这个问题。

如果您的数据库没有销毁信息,那么您可以通过将整个数据库的函数作为值来使用它,从而以与“纯函数”编程值一致的函数方式使用它。

如果在时间 T 数据库声明“ Bob like Suzie”,并且你有一个接受数据库和一个喜欢者的函数 like,那么只要你能在时间 T 恢复数据库,你就有了一个涉及数据库的纯函数式程序。例如:。

# Start: Time T
likes(db, "Bob")
=> "Suzie"
# Change who bob likes
...
likes(db "Bob")
=> "Alice"
# Recover the database from T
db = getDb(T)
likes(db, "Bob")
=> "Suzie"

要做到这一点,你永远不能扔掉你可能使用的信息(这在所有实际意味着你不能扔掉信息) ,因此你的存储需求将单调增加。但是您可以开始将数据库作为一个离散值的线性系列处理,其中后续值通过事务与前面的值相关联。

例如,这就是 达原子背后的主要思想。

  1. 函数式语言的目标不是保持无状态,而是使状态管理变得明确。例如,在哈斯克尔,你可以认为 State monad 是“正常”状态的核心,IO monad 是必须存在于程序之外的状态的表示。这两个单子都允许您(a)显式地表示有状态操作,(b)通过使用引用透明工具组合它们来构建有状态操作。

  2. 您引用了许多 ORM,根据它们的名称,这些 ORM 将数据库抽象为对象集。事实上,这不是关系数据库里的信息所代表的!根据它的名称,它表示关系数据。SQL 是一种代数(语言) ,用于处理关系数据集上的关系,实际上它本身是非常“功能化”的。我提出这一点是为了考虑到: (a) ORM 并不是映射数据库信息的唯一方式; (b)对于某些数据库设计来说,SQL 实际上是一种相当不错的语言; (c)函数式语言通常具有关系代数映射,这种映射以惯用的方式(在哈斯克尔的例子中,是类型检查)暴露了 SQL 的威力。

我得说大多数口齿不清是穷人的实用语言。它完全可以根据现代功能实践来使用,但是由于它不需要这些功能,社区就不太可能使用它们。这导致了一系列方法的混合,这些方法可能非常有用,但肯定会掩盖纯函数式接口仍然可以有意义地使用数据库的原因。

数据库是在无状态 API 中跟踪状态的完美方式。如果您订阅了 REST,那么您的目标是编写与数据存储(或其他后端)交互的无状态代码,该数据存储以透明的方式跟踪状态信息,这样您的客户机就不必这样做了。

对象关系映射器(Object-Relational Mapper)的思想是,将数据库记录作为对象导入,然后对其进行修改,这种思想对函数式编程和面向对象编程同样适用和有用。需要注意的一点是,函数式编程不会就地修改 对象,但是数据库 API 可以允许您就地修改 记录。您的客户端的控制流如下所示:

  • 将记录作为对象导入(此时数据库 API 可以锁定该记录) ,
  • 根据它的内容阅读对象和分支,
  • 用你想要的修改来打包一个新的对象,
  • 将新对象传递给适当的 API 调用,该调用更新数据库上的记录。

数据库将用您的更改更新记录。纯函数式编程可能不允许重新分配变量 在你的计划范围内,但是您的数据库 API 仍然允许就地更新。

数据库和函数式编程可以融合。

例如:

Clojure 是一种基于关系数据库理论的函数式编程语言。

               Clojure -> DBMS, Super Foxpro
STM -> Transaction,MVCC
Persistent Collections -> db, table, col
hash-map -> indexed data
Watch -> trigger, log
Spec -> constraint
Core API -> SQL, Built-in function
function -> Stored Procedure
Meta Data -> System Table


注意: 在最新的 spec2中,spec 更像 RMDB。 参见: < a href = “ https://github.com/clojure/spec-alpha2/wiki/Schema-and-select”rel = “ nofollow noReferrer”> spec-alpha2 wiki: Schema-and-select

我主张: 在散列映射之上构建一个关系数据模型,以实现 NoSQL 和 RMDB 优势的组合。这实际上是 posttresql 的反向实现。

鸭子打字: 如果它看起来像鸭子,叫起来像鸭子,那么它一定是鸭子。

如果 clojure 的数据模型类似于 RMDB,clojure 的工具类似于 RMDB,Clojure 的数据操作类似于 RMDB,那么 clojure 必须是 RMDB。

Clojure 是一种基于关系数据库理论的函数式编程语言

一切都是 RMDB

基于哈希映射(NoSQL)实现关系数据模型和编程