如何在函数式编程中使用多态?

Ale*_*hin 13 oop polymorphism functional-programming clojure

如何在函数式编程中使用多态(使用动态类型系统)?

让我们考虑以下示例(首先在FP中的OOP秒中).该程序非常简单 - 有数字列表,我们需要绘制所有这些,不同的数字使用不同的绘图算法.

在OOP中,它可以简单地完成,但如何在FP中完成?特别是在具有动态类型系统的语言中,如Scheme,Clojure(在编译时没有静态类型解析)?

我创建了简单的代码(实时版本http://tinkerbin.com/0C3y8D9Z,按"运行"按钮).我在FP示例中使​​用了if/else开关,但这是一个非常糟糕的方法.如何才能更好地解决这个问题呢?

样本是用JavaScript编写的,但这只是为了简单起见,看到任何带有动态类型系统的函数语言的解决方案会很有趣.

OOP

var print = function(message){document.write(message + "\n<br/>")}

// Object Oriented Approach.
var circle = {
  draw: function(){print("drawing circle ...")}
}
var rectangle = {
  draw: function(){print("drawing rectangle ...")}
}

var objects = [circle, rectangle]
objects.forEach(function(o){
  o.draw()
})
Run Code Online (Sandbox Code Playgroud)

FP

var print = function(message){document.write(message + "\n<br/>")}

// Functional Approach.
var circle = {type: 'Circle'}
var drawCircle = function(){print("drawing circle ...")}

var rectangle = {type: 'Rectangle'}
var drawRectangle = function(){print("drawing rectangle ...")}

var objects = [circle, rectangle]
objects.forEach(function(o){
  if(o.type == 'Circle') drawCircle(o)
  else if(o.type == 'Rectangle') drawRectangle(o)
  else throw new Error('unknown type!')
})
Run Code Online (Sandbox Code Playgroud)

And*_*erg 13

您的"FP"版本不是我认为的惯用FP示例.在FP中,您经常使用变体和模式匹配,在OOP中您使用类和方法分派.特别是,您只有一个draw已在内部执行调度的函数:

var circle = {type: 'Circle'}
var rectangle = {type: 'Rectangle'}

var draw = function(shape) {
  switch (shape.type) {
    case 'Circle': print("drawing circle ..."); break
    case 'Rectangle': print("drawing rectangle ..."); break
  }
}

var objects = [circle, rectangle]
objects.forEach(draw)
Run Code Online (Sandbox Code Playgroud)

(当然,那就是JavaScript.在函数式语言中,你通常会有更优雅和简洁的语法,例如:

draw `Circle    = print "drawing circle..."
draw `Rectangle = print "drawing rectangle..."

objects = [`Circle, `Rectangle]
foreach draw objects
Run Code Online (Sandbox Code Playgroud)

)

现在,平均OO爱好者会看到上面的代码并说:"但是OO解决方案是可扩展的,上面的不是!" 这是正确的,因为您可以轻松地为OO版本添加新形状,而不必触摸任何现有形状(或其draw功能).使用FP方式,您必须进入并扩展该draw功能以及可能存在的所有其他操作.

但那些人没有看到的是反过来也是如此:FP解决方案是可扩展的,而OO则不是!也就是说,当您在现有形状上添加新操作时,您不需要触摸任何形状定义或现有操作.您只需添加另一个函数,而使用OO,您必须去修改每个类或构造函数以包含新操作的实现.

也就是说,就模块性而言,这里存在二元论.沿着两个轴实现同时可扩展性的理想在文献中称为"表达问题",并且虽然存在各种解决方案(尤其是在函数语言中),但它们通常更复杂.因此,在实践中,您通常需要决定一个维度,具体取决于哪个维度更可能对手头的问题很重要.

功能版还有其他优点.例如,它可以简单地扩展到多个调度或更复杂的案例区别.当实现复杂的算法并且不同的情况是相互关联的时,它也是优选的,因此您希望将代码放在一个地方.根据经验,无论何时开始在OO中使用访问者模式,功能样式的解决方案都会更合适(而且更容易).

一些进一步的评论:

  • 程序组织中的这种不同偏好不是FP的核心思想.更重要的是阻止可变状态,并鼓励高度可重用的高阶抽象.

  • OO社区有这样的习惯,即为每个旧观念发明新的(嗡嗡声)单词.它使用术语"多态性"(与其他地方完全不同)就是这样一个例子.它只是说能够在没有静态知道被叫者的情况下调用函数.您可以在函数为一等值的任何语言中执行此操作.从这个意义上讲,您的OO解决方案也非常实用.

  • 您的问题与类型无关.惯用语OO和惯用FP解决方案都使用无类型或类型语言.


mob*_*yte 4

OO 多态性不是函数式编程的一部分。然而,一些函数式语言(例如 clojure)具有 oo 多态性。

另一种多态性是multimethods

(def circle {:type :circle
             :radius 50})

(def rectangle {:type :rectangle
                :width 5
                :height 10})

(defmulti draw :type)

(defmethod draw :circle [object]
  (println "circle: radius = " (:radius object)))

(defmethod draw :rectangle [object]
  (println "rectangle: "
           "width = " (:width object)
           "height = " (:height object)))

(doseq [o [rectangle circle]] (draw o))
=> rectangle:  width =  5 height =  10
   circle: radius =  50
Run Code Online (Sandbox Code Playgroud)

或者你可以使用函数式风格

(defn circle [] (println "drawing circle ..."))
(defn rectangle [] (println "drawing rectangle ..."))

(def objects [circle rectangle])

(doseq [o objects] (o))
=> drawing circle ...
   drawing rectangle ...
Run Code Online (Sandbox Code Playgroud)

  • @mobyte - 实际上,我想我在这里看到了问题。Alexey 的代码只有一个成员用于他的“square”和“circle”:“draw”。我认为他假设会涉及更多状态(例如 x 和 y 坐标),但为了简单起见,在示例中没有包含它们。如果他们真的只有单一的“draw”方法,那么你是完全正确的,一流的函数是 100% 等效的。如果涉及其他状态,那么多重方法会更合适。 (2认同)