主流的面向对象和lisp广义函数式的面向对象到底有什么不同

我没有深入学习过主流的面向对象技术,所以不了解在实践中,这两种套路到底有何差异,了解的同学请发表一下观点,谢啦。

1 个赞

除了“发送消息”改成了“应用函数”以外,没有啥本质上的不同

可以看下这个

https://www.researchgate.net/publication/234819179_CommonLoops_Merging_Lisp_and_Object-Oriented_Programming

当年 Lisp Machine 是有一套基于消息传递的 OOP 系统,叫 Flavors。CLOS 其实在 CommonLoops 基础上吸收了 Flavors 的一些特性。


顺带吐槽一下,当年我为了 Emacs 学了 Common Lisp,其实 Emacs Lisp 和 CL 差异还是挺大的,而且我怀疑 cl-lib 作者和 eieio 作者对 CL 的理解不是特别高,可能只是看过 CL 标准的程度……反倒是 Wanderlust 自己撸的 luna 一看就知道作者 CL 水平挺高的。

6 个赞

EAF的python代码就是面向对象的,你可以通过继承的方式,用子类扩展父类的特性同时共享父类的属性和函数,也可以通过子类同名函数重新实现来覆盖父类同名函数行为。

函数式语言没有继承概念,主要靠函数组合来完成逻辑。函数式语言的优势是lambda支持的非常好,可以把函数作为一个参数传来传来去(面向对象语言主要传递对象拷贝或引用),像haskell这种存粹函数式语言可以通过等式两边消除参数的形式来实现类似数学公式的简化写法,比如 f = g · h · k 的形式来实现函数抽象。

面向对象语言比较适合大多数世界的应用软件编写,强调数据结构和模块关系的提前设计,通过继承封装可以大幅度减少每个层次的逻辑复杂度。

函数式语言比较适合算法实现和数学推演,比较受理论研究者青睐,当然也有emacs这种拿lisp实现运行时光标和文本操作的清流。

2007年的时候受haskell影响觉得函数式编程语言就是高级,是未来,实际用下来函数式语言并不适合用来写GUI图形库实现。后来认识到,世界是多元化的,编程语言也是,再厉害的语言都有新来的程序员造新的轮子说自己的语言最好。

最后采用多学习不同编程语言的策略,不同场景用最合适的编程语言,emacs就用lisp,图形库和系统库就用python,网络后端就用golang,高性能程序用rust,驱动老老实实用c,画界面js生态最强大,用别的语言都是浪费时间。

5 个赞

在工程实践中,像流程这样的东西,面向对象编程是怎么处理的?直接这个函数还是这个没有属性的类?

除非是 Java 这种没得选的,一般取决于你需不需要代码复用,不需要就不用 OOP。

你把《python核心编程》这本书看完吧,不要学最存粹的oop语言java,写代码太繁琐。

你不系统性的学习和实践,你的问题都是碎片化的,没法告诉你怎么弄。

意思是用混合模式,模型概念上像对象的就用类,模型概念上像流程这种类似过程的,就用函数?

这本书我买了,不过没心思看下去,因为里面的东西,我可能这辈子也用不到 :sweat_smile:

如果是“像什么所以用什么的话”,这只是很粗浅的理解 。

应该是,如果你发现自己写的代码有大段落功能重复的话,就应该想办法用抽象减少代码。OOP 也只是一种方法而已。

那就别管了,你都用不上oop,学它干啥?

我只是好奇 :sweat_smile:

因为工作偶尔会和项目经理和工程师打交道,所以我要了解一点,防止被忽悠 :rofl:

感觉也没什么不同,最近学了一点go语言,有时感觉是面向对象语言,有时又感觉不是,和c++,java这些面向对象区别太大,我其实也没必要纠结这个问题

没有 CL 那种 symbol based namespace, CLOS 怎么整都难受. 我用 Guile 的 GOOPS 也不能完全实现全功能的 mixin, 得时刻提防 slot 名字的碰撞. 一碰撞直接就覆盖了

另外说到 MOP, 感觉实在没什么人用.

CLOS我只稍微用过一下下,感觉和主流OOP的主要差别在:

  1. 可以通过多个对象来进行多态方法分派,主流OOP调用方法时只能基于点号前面的那一个对象
  2. 可以支持before, after, around等多种聚合方法,类似ELISP里的defadvice

大佬理解很透彻,术业有专攻,适合的场景使用适合的语言

这感觉像是 Smalltalk 遗留。

在 Smalltalk 里,调用一个对象的xx方法等于对某个对象发送xx消息,对象接收消息后自行决定怎么处理消息,表现出来就是 dynamic dispatch.

但是 multiple dynamic dispatch 是两个对象共同决定如何 handle 这个消息,Smalltalk 那语义表达不出来(发送消息只能有一个目标,不能两个一起发到,一起决定)。折中的方法就是 objectA 先接收消息,把程序逻辑拆开。 再给 objectB 发一个消息, 让 objectB 接收另一个消息,变相实现了 multiple dynamic dispatch(visitor pattern)

3 个赞

“方法”和“消息”是不是同一种东西?就是表达目的/传播信号

面向对象就是主谓宾,函数式就一切都是祈使句

大学以为面向对象比函数式更高级,后面才意识到自己被“传销”了

近期看一些韩国视频带韩语字幕,感觉韩语大多数文字都差不了太多,哪像中文这样大千世界,也足够表达自己的思想吗?

英语的语法跟中文很不同,也有谚语,类似我们的成语?

微积分就是函数式吧?估计函数式创始人(Fortran作者)也是参考了微积分?

我觉得可以先尝试说明 oop, fp 是什么样的关系。

“范式”是公认遵守的“模式”。编程的范式(oop, fp 之类的)是在编程可用的行为上做减法,得出的具有约束性的解决方案。

举例子来讲:

a: foo
bar
baz
if ??? then 
  goto a

这样散装几个语句从上到下执行,goto 随意的控制逻辑的跳转。这其实是个无约束的模式。

接下来禁止 goto ,也就是禁止了逻辑的任意跳转。不能任意跳转的话,就需要对逻辑进行组织和编排。

foo(){}
bar(){}
baz(){
  if ??? then foo() else bar()
}

此时,程序就不在以语句这个维度来构建,而是开始使用“过程”。遵守这个规则就得到了“面向过程”范式。

在上面的方式中,baz 对于 foo 和 bar 还是具有直接的依赖,或者说调用者 baz 决定着 foo 或 bar 被调用。接下来避免使用直接调用控制。

Foo impl I {
    m()
}

Bar impl I {
    m()
}

baz(i: I) {
    i.m()
}

这个时候,baz 的调用行为,就被 i 的数据类型所控制了。确定逻辑的顺序(初始化 i,调用 i.m)和代码的运行顺序(调用 i.m,根据 i 类型分发)是相反的,这个就是控制反转,封装、继承、多态是这个效果在不同角度看的描述。逻辑分发规则的集合就是类,标记具有类属性的数据就是“对象”。程序以对象为维度构建,就是“面向对象”范式。

回到之前的面向过程,假如说在过程的执行中,要避免副作用,给逻辑单元固定的输入就能得到固定的输出,那么就需要避免变量(不仅是具有标签的变量,也保括环境型的变量等等)。理想情况,没有了变量,没有输出的过程就没有了意义。所以每个过程都该有返回值,这样具有返回值的,无副作用的过程,就可以叫做“函数”。以函数为维度构建程序,就是“函数”范式。

所以,

  1. 函数式和面向对象并不是完全对立的东西,如果你用函数构建程序,同时使用着对象来反转控制,那应该是可以叫做面向对象+函数式,但这个是约束的并集,不是功能的并集。
  2. 当然,面向对象可以不函数式,使用变量,不要求返回值等等。
  3. 语言的约束不等于范式的执行情况,在 C 中用大量 goto 破坏掉过程的单元,不应算是面向过程。反过 C 也可以做面向对象。
  4. 面向对象要的控制反转的效果,并不一定要面向对象才可以。比如基于值做分发,也可以达到效果,面向对象只是这个问题上一个最出名的解决方案。

最后, 主流的面向对象和lisp广义函数式的面向对象到底有什么不同?

没有什么本质的区别,主流的面向对象、函数式的面向对象、甚至不面向对象,都可以满足控制反转的需求。大概仅仅是推崇写法上的区别而已。但都同为面向对象的语言,所具有的表达力和语言的限制还是不同的,但这是语法设计的问题而不是编程范式的问题。

7 个赞

感觉你像个语言考古学家,能了解这么深,一定要拿个Phd啊。