一个朋友试图说服我clojure的好处,我正在尽我最大的努力来理解该语言。我浏览了许多网站,并浏览了《勇敢与真实的Clojure》一书,但我仍然缺少一些东西。
我的背景是面向对象的编程,而我的第二个本性是,程序对象代表真实世界的对象。我已经读过clojure不是OO,但是我不明白clojure是做什么的。
这里是一个示例(简化了很多)。我正在编写用于计划退休收入的应用程序。它有一个储蓄帐户类和一个养老金类。储蓄账户有余额和利率。它具有一种Withdraw方法,该方法可以在一年内取出指定的金额(通过的参数)。余额每年减少提取的金额,而利息增加。您的提款额永远不能超过余额。
退休金类的年收入和百分比在逐年增加。这也有提款方法,但提款金额始终是年收入(每年增加百分比)。也就是说,其计算方式与储蓄帐户不同。
在我的OO程序中,这两个类都实现了定义Withdraw方法的协议,因此我的代码可以以相同的方式处理它们,而无需考虑它们之间的差异。
在clojure世界中,什么代表储蓄帐户和养老金。即,什么结构将实例的数据和对该数据进行操作的方法收集在一起?在OO中,该类定义数据和方法。该实例是数据的唯一出现。我定义类的代码文件告诉我有关该类表示的对象的所有信息。 clojure如何做到这一点,或者它做什么呢?
答案 0 :(得分:5)
首先让我告诉你,我完全理解你的感受,我不得不经历同样的情况。而且,我还要告诉您,学习Clojure(函数式编程)是完全值得的(当然,这是个人评估)。
Clojure确实具有OOP工具,例如,您确实有defrecord
和defprotocol
。但从本质上讲,它比面向对象的功能更强大,并且只有在开始进行功能性的思考和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外壳,则与命令管道/链接非常相似。
请注意,您还可以通过声明一个Withdraw
multimethod来简化上述功能。而且,如果您愿意,还可以使用datatypes来更严格地定义数据,并使用protocols来实现这些记录的接口,这与您在Java中的操作更加接近。您需要了解的要点是,尽管Java围绕类,而Clojure围绕函数和不可变数据,并且pure越多,函数越好。
答案 1 :(得分:5)
程序对象代表现实世界对象是我的第二天性
我认识的大多数面向对象的程序员都不program that way。当我使用OO语言编程时,我不会那样编程。除了gamedev的 possible 例外,面向对象程序中的 most 对象仅具有与业务域实体最紧密的连接,原因与 most 我的SQL表中往往只有最弱的业务域实体连接。
商人不关心URIProcurerAbstractFactoryManagers或桥表,因为它们没有现实世界中的模拟物。
我提到上面的内容(以及有关SQL表的内容)是因为要点是,我们根据软件工件创建的大多数内容必然但间接地与解决问题有关。诸如银行和国家慈善机构这样的完全无关的东西的HTTP后端看起来非常相似(以至于文件/目录/表名可能是您正在寻找的最佳线索)。我们写的内容更多地与问题的类型的约束有关,而不是问题本身的细节。
Clojure是一种不同的问题建模方式,同样,关系表是一种不同的问题建模方式。
您可以尝试使用与处理对象相同的方式来处理clojure数据结构。您可以尝试像对待对象一样对待关系表。但这并不能很好地发挥作用(请参阅ORM的棘手和困扰历史)。 您可以尝试将所有面向对象的程序对象都视为现实世界中的类似物,但这也不会使您走得太远。
您必须学习有关问题的思考的新方法,而我甚至认为您将无法选择最佳除非您至少对它们中的大多数至少有一点点熟悉(对尝试学习新的东西表示敬意!)。
答案 2 :(得分:0)
@ M0skit0有一个很好的答案。我想特别提及。您问:
在我的OO程序中,这两个类都实现了定义Withdraw方法的协议
您可以拥有无需封装的类方法。仔细查看protocols。如果您的大脑已经考虑过面向对象,那么您应该考虑clojure的方法是让您使用协议,但不必这样做。因此,术语“点菜多态性”。在该词条上寻找一些唯一的资源,您会发现很多人会感到困惑,但您可能会发现在脑中说话。