【问题标题】:Trying to understand clojure试图理解clojure
【发布时间】:2019-02-04 11:54:13
【问题描述】:

一位朋友试图让我相信 clojure 的好处,我正在尽我所能理解该语言。我浏览了很多网站并浏览了“Clojure for the Brave and True”一书,但我仍然缺少一些东西。

我的背景是面向对象编程,对我来说,程序对象代表现实世界的对象是我的第二天性。我读过 clojure 不是 OO,但我不明白 clojure 是做什么的。

这是一个示例(非常简化)。我正在编写一个用于计划退休收入的应用程序。它有一个储蓄账户类和一个养老金类。储蓄账户有余额和利率。它有一个 Withdraw 方法,可以在一年内取出指定的金额(传递的参数)。余额每年减少提取的金额并增加利息金额。您提取的金额永远不能超过余额。

养老金类有年收入和年增长率。这也有提款方法,但提款金额始终是年收入(每年增加百分比)。即它的计算方式与储蓄账户不同。

在我的 OO 程序中,这两个类都实现了定义 Withdraw 方法的协议,因此我的代码可以以相同的方式处理它们,而无需考虑它们的差异。

在 clojure 世界中,代表储蓄账户和养老金的东西。即,哪些结构将实例的数据以及对这些数据进行操作的方法收集在一起?在 OO 中,类定义了数据和方法。实例是数据的唯一出现。我定义类的代码文件告诉我关于类所代表的对象的所有信息。 clojure 是如何做到这一点的,或者它做了什么?

【问题讨论】:

标签: clojure


【解决方案1】:

首先让我告诉你,我完全理解你的感受,我不得不经历同样的情况。我还要告诉你,学习 Clojure(函数式编程)是完全值得的(当然这是个人评估)。

Clojure 确实有 OOP 工具,例如你有 defrecorddefprotocol。但从本质上讲,它比面向对象更具有功能性,只有当您开始以功能性方式思考和immutability 时,您才能获得真正的好处。

要编写惯用的 Clojure,您需要将思维从对象转换为函数。在函数式语言中,一切都围绕着函数,而不是数据(对象)。数据使用简单的数据结构表示,例如地图。您不会像在 OOP 中那样将函数与其数据绑定。在您的情况下,您只需要一个数据结构来表示这些数据,而不是一个类 Savings Account 和一个类 Pension,例如一个地图:

(def saving-account {:balance 0.0, :interest-rate 0.0})
(def pension {:annual-income 0.0, :annual-increase 0.0})

Withdraw 方法只是针对每种情况的一个函数,例如:

(defn withdraw-from-saving-account [sa amount] ( <return updated saving account map> ))
(defn withdraw-from-pension [p] ( <return updated pension map> ))

在 Clojure 中,您通常希望保持一切不可变,因此上述函数将返回一个新的更新地图,并且不会修改现有地图中的任何现有状态。当来自像 Java 的 OOP 这样沉重的可变状态范式时,这需要一些时间来适应。惯用的 Clojure(与大多数函数式编程语言一样)很大程度上基于函数组合(这意味着尽可能短的单任务函数)和非常有限的变量使用。如果您曾经使用过 *nix shell,这与命令管道/链接非常相​​似。

请注意,您还可以通过声明单个Withdraw multimethod 来简化上述函数。如果您愿意,您还可以使用datatypes 更严格地定义数据并使用protocols 为这些记录实现接口,这更接近您在Java 中所做的。您需要了解的主要一点是,Java 是围绕类旋转的,而 Clojure 是围绕函数和不可变数据旋转的,并且函数的 pure 越多越好。

【讨论】:

  • ... 简单的数据结构,例如地图 地图很简单(至少对用户而言),但并不简单。
  • 谢谢@m0skit0。但是,如果这是在 clojure 中执行此操作的方式,那么在大型程序中使用什么来构造代码以使其可读?如果在 OO 中我有 20 个类,每个类有 15 个方法,那么我的程序会根据类进行划分(每个类一个代码文件)。从您所写的内容来看,在 clojure 中我似乎有一个包含 20 个 def 的列表。另一个 15 退出 defun 的列表。并且进一步列出了 15 个长的其他函数,每个函数都有一个单独的版本用于每个 def 的。如果 clojure 没有在函数层之上执行结构,你如何实现可读性?
  • @Julian7 clojure 有一个独特的命名空间抽象。关于 Java 的最大抱怨之一是类的概念过于重载:它是 Java 为您提供的唯一抽象(在 Java 8 之前),因此您必须将它用于所有会产生阻抗不匹配的东西:类是不好的替代品一个适当的命名空间。我不知道个人理财是最容易理解的例子,但我会有不同的命名空间,其中包含数据和函数来操作这些数据,以及一个核心命名空间来将它们拼接到一个外部 API 中。
  • @Julian7 查看Clojure, Made Simple,尤其是 49 分钟的部分,其中包含 Rich Hickey 对价值等级的咆哮。 “这些东西以文本形式通过 HTTP 传输!我们如何将其变成 [一个 HttpServletRequest]?发生了什么?”
  • defndefun有什么区别?
【解决方案2】:

程序对象代表现实世界的对象是我的第二天性

我认识的大多数面向对象的程序员都没有program that way。当我用 OO 语言编程时,我不会那样编程。除了gamedev的可能例外,面向对象程序中的大多数对象与业务领域实体的连接最薄弱,原因与大多数 我的 SQL 表中往往只有最脆弱的业务域实体连接。

业务人员不关心 URIProcurerAbstractFactoryManager 或桥接表,因为他们没有现实世界的模拟。

我提到上述内容(以及关于 SQL 表的部分)是因为重点是,我们根据软件工件创建的大部分内容都必然但间接地与解决问题相关。完全不相关的事物的 HTTP 后端,比如银行和国家慈善机构,看起来非常相似(文件/目录/表名称可能是您正在查看的最佳线索)。我们所写的更多内容与问题的类型的约束有关,而不是问题本身的细节。

Clojure 是一种对问题建模的不同方式,就像关系表是一种对问题建模的不同方式一样。

您可以尝试像对待对象一样对待 clojure 数据结构。您可以尝试像对待对象一样对待关系表。但它不会很好地工作(参见 ORM 的棘手和麻烦的历史)。 您可以尝试将所有面向对象的程序对象视为现实世界的类似物,但这也不会让您走得太远。

你必须学习一种思考问题的新方法,而且我什至会说你无法选择最好的除非你至少对它们中的大多数都非常熟悉(非常感谢你尝试学习一个新的!)。

【讨论】:

    【解决方案3】:

    @M0skit0 有一个很好的答案。我想提出一个具体的提及。你问:

    在我的 OO 程序中,这两个类都实现了定义 Withdraw 方法的协议

    你可以在没有封装的情况下拥有你的类方法。仔细查看protocols。如果您的大脑已经在 OO 中思考,那么您应该考虑 clojure 的方法是让您使用协议,但您不必这样做。因此,术语“点菜多态性”。仅查找该术语的一些资源,您会发现很多人会感到困惑但您可能会发现与您的大脑对话的良好讨论。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-06
      • 2023-03-07
      • 2011-03-28
      • 2011-11-27
      • 2017-08-11
      • 1970-01-01
      相关资源
      最近更新 更多