函数编程与带类的OOP相比


32

最近,我对函数式编程的某些概念感兴趣。我已经使用OOP一段时间了。我可以看到如何在OOP中构建一个相当复杂的应用程序。每个对象都会知道该对象执行的操作。还是父母班上的任何事情都可以。因此,我可以简单地告诉Person().speak()别人说话。

但是如何在函数式编程中做类似的事情?我看到函数是头等舱物品。但是该功能只做一件事。我是否可以简单地使用一个say()浮动的方法,并使用等效的Person()参数来调用它,以便使我知道在说些什么呢?

因此,我可以看到简单的事情,在函数式编程中,我将如何做OOP和对象的比较,从而可以模块化和组织代码库?

作为参考,我对OOP的主要经验是Python,PHP和一些C#。我正在查看的具有功能特性的语言是Scala和Haskell。虽然我倾向于Scala。

基本示例(Python):

Animal(object):
    def say(self, what):
        print(what)

Dog(Animal):
    def say(self, what):
        super().say('dog barks: {0}'.format(what))

Cat(Animal):
    def say(self, what):
        super().say('cat meows: {0}'.format(what))

dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')

Scala被设计为OOP + FP,因此您不必选择
Karthik T

1
是的,我知道,但出于智力原因,我也想知道。我无法在功能语言的等效对象上找到任何东西。至于scala,我仍然想知道我何时/何地/如何使用oop功能,但是恕我直言是另一个问题。
2012年

2
“特别强调的是,IMO是我们不维护状态的概念。”:这是错误的概念。FP不使用状态是不正确的,而是FP以不同的方式处理状态(例如,Haskell中的monad或Clean中的唯一类型)。
乔治(Giorgio)


Answers:


21

您在这里真正要问的是如何在功能语言中实现多态,即如何创建基于其参数表现不同的功能。

注意,函数的第一个参数通常等同于OOP中的“对象”,但是在函数语言中,您通常希望将函数与数据分开,因此“对象”可能是纯(不变)数据值。

通常,功能语言为实现多态性提供了多种选择:

  • 类似多方法的东西,它会根据检查提供的参数来调用不同的函数。这可以在第一个参数的类型上完成(实际上与大多数OOP语言的行为相同),但是也可以在参数的其他属性上完成。
  • 包含一流函数作为成员的原型/类对象数据结构。因此,您可以在“狗”和“猫”数据结构中嵌入“说”功能。实际上,您已经使代码成为了数据的一部分。
  • 模式匹配 -模式定义逻辑内置在函数定义中,并确保不同参数的行为不同。在Haskell中常见。
  • 分支/条件 -等同于OOP中的if / else子句。可能不是高度可扩展的,但在可能的值集有限的情况下(例如,该函数是否传递了数字,字符串或null?

例如,这是使用多种方法对您的问题进行的Clojure实现:

;; define a multimethod, that dispatched on the ":type" keyword
(defmulti say :type)  

;; define specific methods for each possible value of :type. You can add more later
(defmethod say :cat [animal what] (println (str "Car purrs: " what)))
(defmethod say :dog [animal what] (println (str "Dog barks: " what)))
(defmethod say :default [animal what] (println (str "Unknown noise: " what)))

(say {:type :dog} "ruff")
=> Dog barks: ruff

(say {:type :ape} "ook")
=> Unknown noise: ook

请注意,此行为不需要定义任何显式类:常规映射可以正常工作。调度函数(在这种情况下为:type)可以是参数的任意函数。


并非100%清晰,但足以了解您的前进方向。我可以将其视为给定文件中的“动物”代码。分支/条件部分也很好。我还没有考虑过if / else的替代方法。
skift

11

这不是直接的答案,也不一定是100%准确的,因为我不是函数语言专家。但无论哪种情况,我都会与您分享我的经验...

大约一年前,我和你在同一条船上。我已经做过C ++和C#,而且我所有的设计都总是非常注重OOP。我听说过FP语言,在线阅读了一些信息,翻阅了F#书,但仍然无法真正掌握FP语言如何代替OOP或在总体上还是有用的,因为我看到的大多数示例都太简单了。

对我来说,当我决定学习python时,“突破”就来了。我下载了python,然后转到项目euler主页,并开始一个接一个地处理问题。Python不一定是FP语言,您当然可以在其中创建类,但是与C ++ / Java / C#相比,Python确实具有更多的FP构造,因此当我开始使用它时,我做出了一个有意识的决定,除非绝对没有必要,否则请定义一个类。

我对Python感到有趣的是,获取函数并“缝合”它们以创建更复杂的函数是多么容易和自然,最终您的问题仍然可以通过调用单个函数来解决。

您指出,编码时应遵循单一责任原则,这是绝对正确的。但是,仅因为函数负责一项任务,并不意味着它只能完成绝对的最低要求。在FP中,您仍然具有抽象级别。因此,您的高级功能仍然可以执行“一个”操作,但是它们可以委派给较低级的功能以实现有关如何实现“一个”操作的详细信息。

但是,FP的关键是您没有副作用。只要将应用程序视为具有定义的一组输入和一组输出的简单数据转换,就可以编写可实现所需功能的FP代码。显然,并不是每个应用程序都能很好地适应此模型,但是一旦开始执行,您会惊讶地发现有多少应用程序适合。这就是我认为Python,F#或Scala发光的地方,因为它们为您提供FP构造,但是当您确实需要记住自己的状态并“引入副作用”时,您总是可以依靠真实的和经过实践检验的OOP技术。

从那时起,我编写了很多python代码作为实用程序和其他用于内部工作的帮助程序脚本,其中一些脚本的扩展范围很广,但是通过记住基本的SOLID原理,大部分代码仍然非常易于维护和灵活。就像在OOP中,您的界面是一个类,并且在重构和/或添加功能时在类中四处移动一样,在FP中,您对功能执行的操作完全相同。

上周,我开始使用Java进行编码,从那时起,几乎每天我都在想起,在OOP中,我必须通过使用覆盖函数的方法声明类来实现接口,在某些情况下,我可以使用简单的lambda表达式,例如,我编写的用于扫描目录的20-30行代码在Python中将是1-2行,并且没有类。

FP本身是高级语言。在Python中(对不起,这是我唯一的FP经验),我可以将列表理解与另一个lambda和其他东西组合在一起,整个过程只有3-4行代码。在C ++中,我绝对可以完成同一件事,但是由于C ++是较低级的,所以我必须编写比3-4行更多的代码,并且随着行数的增加,我的SRP培训将开始,我将开始考虑如何将代码分成较小的部分(即更多功能)。但是出于可维护性和隐藏实现细节的考虑,我将所有这些功能都放在同一个类中,并将它们设为私有。在那里,有……我刚刚创建了一个类,而在python中,我会写成“ return(.... lambda x:......)”


是的,它不能直接回答问题,但仍然是一个很好的答复。当我用python编写较小的脚本或程序包时,我也不总是使用类。很多时候只是以包装形式提供就非常合适。特别是如果我不需要状态。我也同意列表理解同样有用。自从阅读FP以来,我已经意识到它们可以提供的强大功能。与OOP相比,这使我想了解更多关于FP的知识。
2012年

好答案。与站在泳池旁的所有人交谈,但不确定是否将脚趾浸入水中
Robben_Ford_Fan_boy 2012年

还有Ruby ...它的设计理念之一是将代码块作为参数的方法,通常是可选方法。鉴于语法干净,这种方式很容易编码。在C#中很难像这样思考和编写。C#的功能在功能上是冗长而令人迷惑的,它使语言感到shoe脚。我喜欢Ruby有助于在功能上更轻松地进行思考,从而在稳固的C#思想框中看到潜能。归根结底,我认为功能和面向对象是互补的。我想说Ruby当然是这么认为的。
–radarbob

8

在Haskell中,最接近的是“类”。该类虽然与Java和C ++中的类不同,但在这种情况下将可以满足您的需要。

在您的情况下,这就是您代码的外观。

类动物在哪里 
说::字符串->声音 

然后,您可以使单个数据类型适应这些方法。

实例动物狗在哪里
说s =“树皮” ++ s 

编辑:-在您可以对狗说专业之前,您需要告诉系统狗是动物。

数据Dog = \-这里的东西-\(派生动物)

编辑:-对于威尔克。
现在,如果要在一个函数foo中使用say,则必须告诉haskell foo只能与Animal一起使用。

foo ::(动物a)=> a->字符串->字符串
foo a str =说一个str 

现在,如果您用狗叫foo,它将吠叫;如果您用猫叫,它将喵叫。

主=做 
令d = dog(\-cstr参数-\)
    c =猫  
在show $ foo d“ Hello World”中

您现在不能再有其他任何功能定义了。如果使用非动物的任何东西调用say,则会导致编译错误。


我必须在haskell上有更多的实际知识才能完全理解,但是我想我很明白。我仍然很好奇,尽管这将如何与更复杂的代码库保持一致。
skift

nitpick动物应该大写
Daniel Gratzer 2012年

1
如果say函数仅接受字符串,它如何知道您在Dog上调用它?并且不是仅对某些内置类“派生”吗?
WilQu

6

功能语言使用2种构造来实现多态:

  • 一阶函数
  • 泛型

用这些代码创建多态代码与OOP使用继承和虚拟方法的方式完全不同。虽然这两种功能都可能以您最喜欢的OOP语言(例如C#)提供,但是大多数功能语言(例如Haskell)最多可以使用11种语言。函数很少是非泛型的,并且大多数函数都有函数作为参数。

这样很难解释,这将需要大量时间来学习这种新方法。但是要做到这一点,您需要完全忘记OOP,因为这不是功能世界中的工作方式。


2
OOP与多态有关。如果您认为OOP是关于将函数绑定到数据,那么您对OOP一无所知。
欣快的2012年

4
多态性只是OOP的一个方面,我认为不是OP真正在问的一个方面。
布朗

2
多态性是OOP的关键方面。还有其他一切来支持它。没有继承/虚拟方法的OOP与过程编程几乎完全相同。
欣快的2012年

1
@ErikReppen如果不需要经常“应用接口”,那么您就不做OOP。此外,Haskell也具有模块。
欣快感2012年

1
您并不总是需要界面。但是,当您确实需要它们时,它们将非常有用。而IMO是OOP的另一个重要组成部分。至于在Haskell中使用的模块,就代码组织而言,我认为对于功能语言而言,这可能最接近OOP。至少从我到目前为止所读的内容来看。我知道他们还是很不一样。
2012年

0

这实际上取决于您要完成什么。

如果您只需要一种基于选择性标准组织行为的方法,则可以使用带有功能对象的字典(哈希表)。在python中可能类似于以下内容:

def bark(what):
    print "barks: {0}".format(what) 

def meow(what):
    print "meows: {0}".format(what)

def climb(how):
    print "climbs: {0}".format(how)

if __name__ == "__main__":
    animals = {'dog': {'say': bark},
               'cat': {'say': meow,
                       'climb': climb}}
    animals['dog']['say']("ruff")
    animals['cat']['say']("purr")
    animals['cat']['climb']("well")

但是请注意,(a)没有猫的 “实例”,并且(b)您必须自己跟踪对象的“类型”。

例如:pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]。那么你可以像这样进行列表理解[animals[pet[1]]['say'](pet[2]) for pet in pets]


0

有时可以使用OO语言代替低级语言来直接与计算机交互。C ++当然可以,但是即使对于C#也有适配器等。尽管编写代码来控制机械零件并具有对内存的细微控制,但最好将其保持在尽可能低的水平。但是,如果这个问题与当前的面向对象软件(例如业务线,Web应用程序,IOT,Web服务以及大多数大规模使用的应用程序)有关,那么...

回答(如果适用)

读者可以尝试使用面向服务的体系结构(SOA)。也就是说,DDD,N层,N层,六边形等。我还没有看到大型企业应用程序有效地使用“传统” OO(活动记录或富模型),因为在过去的20年代70年代和80年代就已经描述过。(见注1)

问题不在OP上,但这个问题有两个问题。

  1. 您提供的示例只是为了演示多态性,它不是生产代码。有时,从字面上看就是完全一样的例子。

  2. 在FP和SOA中,数据与业务逻辑分开。也就是说,数据和逻辑不能并存。逻辑进入服务,并且数据(域模型)不具有多态行为(请参见注释2)。

  3. 服务和功能可以是多态的。在FP中,您经常将函数作为参数而不是值传递给其他函数。您可以在OO语言中使用Callable或Func之类的方法进行相同的操作,但是它不会运行得很猖((请参阅注释3)。在FP和SOA中,您的模型不是多态的,只有服务/功能。(见注4)

  4. 在该示例中,存在硬编码的不良情况。我不仅在谈论红色的字符串“狗吠”。我也在谈论CatModel和DogModel本身。如果要添加绵羊,会发生什么?您必须输入代码并创建新代码?为什么?在生产代码中,我宁愿仅看到带有其属性的AnimalModel。最糟糕的是,如果AmphibianModel和FowlModel的属性和处理方式如此不同,则将其替换。

我期望在当前的“ OO”语言中看到以下内容:

public class Animal
{
    public int AnimalID { get; set; }
    public int LegCount { get; set; }
    public string Name { get; set; }
    public string WhatISay { get; set; }
}

public class AnimalService : IManageAnimals
{
    private IPersistAnimals _animalRepo;
    public AnimalService(IPersistAnimals animalRepo) { _animalRepo = animalRepo; }

    public List<Animal> GetAnimals() => _animalRepo.GetAnimals();

    public string WhatDoISay(Animal animal)
    {
        if (!string.IsNullOrWhiteSpace(animal.WhatISay))
            return animal.WhatISay;

        return _animalRepo.GetAnimalNoise(animal.AnimalID);
    }
}

基本流程

您如何从OO中的类转换为函数式编程?正如其他人所说的;您可以,但实际上并非如此。上面的要点是要证明,当您使用Java和C#时,甚至不应该使用类(按照世界的传统意义)。一旦您开始在面向服务的体系结构(DDD,分层,分层,六边形等)中编写代码,您将离功能更近一步,因为您将数据(域模型)与逻辑功能(服务)分开了。

OO语言比FP更近一步

您甚至可以更进一步,将SOA服务分为两种类型。

可选类类型1:入口点的通用接口实现服务。这些将是“不纯的”入口点,可以调用“纯”或“不纯”其他功能。这可能是您来自RESTful API的入口点。

可选类类型2:纯业务逻辑服务。这些是具有“纯”功能的静态类。在FP中,“纯”表示没有副作用。它没有在任何地方显式设置State或Persistence。(见注5)

因此,当您想到面向服务的体系结构中使用的面向对象语言中的类时,它不仅使您的OO代码受益,而且还开始使函数式编程看起来非常容易理解。

笔记

注意1:原始的“丰富”或“活动记录”面向对象设计仍然存在。人们在十年或更早的时候“做正确的事”时,就有许多类似的遗留代码。上次我看到这种代码(正确完成)是来自C ++的视频游戏Codebase,在那里他们可以精确地控制内存并且空间非常有限。更不用说FP和面向服务的体系结构是野兽,不应该考虑硬件。但是它们将不断变化,维护,具有可变数据大小以及其他方面的能力作为优先事项。在视频游戏和机器AI中,您可以非常精确地控制信号和数据。

注2:域模型没有多态行为,也没有外部依赖关系。它们是“隔离的”。这并不意味着它们必须是100%贫血的。如果适用,它们可以具有与其构造和可变属性更改相关的许多逻辑。请参见Eric Evans和Mark Seemann撰写的DDD“值对象”和实体。

注3:Linq和Lambda是很常见的。但是,当用户创建新功能时,他们很少使用Func或Callable作为参数,而在FP中,看到一个没有遵循该模式的功能的应用会很奇怪。

注4:不要将多态与继承混淆。CatModel可以继承AnimalBase来确定Animal通常具有哪些属性。但正如我所展示的,这样的模型是Code Smell。如果看到此模式,则可以考虑将其分解并将其转换为数据。

注5:纯函数可以(也可以)接受函数作为参数。传入的函数可能不纯,但可能是纯函数。出于测试目的,它始终是纯净的。但是在生产中,尽管将其视为纯净的,但可能会产生副作用。这并不会改变纯函数是纯函数这一事实。虽然参数功能可能不正确。不要混淆!:D


-2

你可以做这样的事情.. php

    function say($whostosay)
    {
        if($whostosay == 'cat')
        {
             return 'purr';
        }elseif($whostosay == 'dog'){
             return 'bark';
        }else{
             //do something with errors....
        }
     }

     function speak($whostosay)
     {
          return $whostosay .'\'s '.say($whostosay);
     }
     echo speak('cat');
     >>>cat's purr
     echo speak('dog');
     >>>dogs's bark

1
我还没有投反对票。但是我的猜测是,因为这种方法既没有功能,也没有面向对象。
Manoj R

1
但是所传达的概念接近于功能编程中使用的模式匹配,即$whostosay成为确定执行内容的对象类型。可以修改以上内容以额外接受另一个参数,$whattosay以便支持它的类型(例如'human')可以使用它。
syockit
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.