函数式和面向对象编程有什么区别?

2023-11-05

函数式编程 (Functional Programming) 和 面向对象编程 (Object Oriented Programming) 是两个主流的编程范式,他们有各自独特的闪光点,比如函数式编程的数据不可变惰性求值,面向对象编程的继承多态等。这些语言特性上的区别,可以参考之前的文章,这篇文章主要从实现相同功能的角度,来对比这两种编程范式,他们在实现上的逻辑是截然相反的

初步实现

在函数式编程中,代码逻辑通常是按照要做什么。而在面向对象编程中,通常是把代码逻辑抽象成 class,然后给这些 class 一些操作。这么说起来很抽象,用下面这个例子来详细说明。

假设我们要用 函数式编程 和 面向对象编程 来分别实现下面这些功能:

eval toString hasZero
Int
Add
Negate

表格左列 Int, Add, Negate 是三个变式 (Variant),eval, toString, hasZero 是三种操作,这里要做的是填满这个表格,分别实现三个变式的三种操作。

函数式编程实现

这里用 ML 来做函数式编程的实现,即使没用过这门语言,应该也能读懂大概意思。

datatype exp =
    Int    of int
  | Negate of exp
  | Add    of exp * exp

exception BadResult of string

fun add_values (v1,v2) =
    case (v1,v2) of
    		(Int i, Int j) => Int (i+j)
      | _ => raise BadResult "non-values passed to add_values"

fun eval e =
    case e of
    		Int _       => e
      | Negate e1   => (case eval e1 of
                          Int i => Int (~i)
      | _ => raise BadResult "non-int in negation")
      | Add(e1,e2)  => add_values (eval e1, eval e2)

fun toString e =
    case e of
        Int i 			=> Int.toString i
      | Negate e1   => "-(" ^ (toString e1) ^ ")"
      | Add(e1,e2)  => "("  ^ (toString e1) ^ " + " ^ (toString e2) ^ ")"

fun hasZero e =
    case e of
      	Int i 			=> i=0
      | Negate e1   => hasZero e1
      | Add(e1,e2)  => (hasZero e1) orelse (hasZero e2)

在函数式编程中,先定义了一个数据类型 (datatype) 来表示 Int, Negate, Add,这样定义的目的是什么呢?举个表达式的例子:

  • Int 代表一个 int 的数据,比如 Int(2)
  • Negate 代表 Int 的负数,比如 Negate(Int(2)))
  • Add 代表两个 Int 相加,比如 Add((Int(2), Int(3))

然后再分别实现三个操作 eval, toString, hasZero:

  • eval 是给一个表达式求值,比如给 Negate 求值,eval(Negate(Int(2))) = Int(-2) ,给 Add 求值,eval(Add(Int(2), Int(3))) = Int(5)
  • toString 是把这个表达式输出成字符串,比如 toString(Add(Int(2), Int(3))) = "2 + 3"
  • hasZero 是判断表达式有没有 0。

再看刚刚这句话函数式编程的代码逻辑通常是按照要做什么,这里的主体是三个操作,eval, toString 和 hasZero,所以三个分别是一个函数,在函数里去实现三种变式怎么操作。

可以说,函数式编程式纵向的填满了上面的表格。

面向对象编程

这里用 Ruby 来实现。

class Exp
end

class Value < Exp
end

class Int < Value
  attr_reader :i
  def initialize i
    @i = i
  end
  def eval # no argument because no environment
    self
  end
  def toString
    @i.to_s
  end
  def hasZero
    i==0
  end
end

class Negate < Exp
  attr_reader :e
  def initialize e
    @e = e
  end
  def eval
    Int.new(-e.eval.i) # error if e.eval has no i method
  end
  def toString
    "-(" + e.toString + ")"
  end
  def hasZero
    e.hasZero
  end
end

class Add < Exp
  attr_reader :e1, :e2
  def initialize(e1,e2)
    @e1 = e1
    @e2 = e2
  end
  def eval
    Int.new(e1.eval.i + e2.eval.i) # error if e1.eval or e2.eval has no i method
  end
  def toString
    "(" + e1.toString + " + " + e2.toString + ")"
  end
  def hasZero
    e1.hasZero || e2.hasZero
  end
end

< 在 Ruby 里是继承的意思,class Int < Value 表示 Int 继承了 Value,Int 是 Value 的 Subclass。

可以看到面向对象编程组织代码的方式和之前的完全不一样。这里把 Int, Negate, Add 抽象成了三个 class,然后分别给每个 class 加上 eval, toString, hasZero 三个方法。这也是刚刚那句话的说法 面向对象编程把代码逻辑抽象成 class,然后给这些 class 一些操作,这里的主体是 Int, Negate, Add 这三个 class。

可以说,面向对象编程是横向的填满了上的表格。

通过这个对比,可以知道 函数式编程 和 面向对象编程 是两种相反的思维模式和实现方式。这两种方式对代码的扩展性有什么影响呢?

扩展实现

eval toString hasZero absolute
Int
Negate
Add
Multi

在上面那个例子的基础上,我们再加一行一列,增加 Multi 这个变式,表示乘法,增加 absolute 这个操作,作用是求绝对值。这会怎么影响我们的代码呢?

函数式编程

在函数式编程中,要增加一个操作 absolute 很简单,只要添加一个新的函数,不用修改之前的代码。但是要增加 Multi 比较麻烦,要修改之前的所有函数。

面向对象编程

和函数式编程相反的,在这里增加一个 Multi 简单,只要添加一个新的 class,但是增加 absolute 这个操作就要在之前的每一个 class 做更改。

选择用 函数式编程 还是 面向对象编程 的一个考量因素是以后将会如何扩展代码,对之前代码的更改越少,出错的概率越小。

Binary Methods

前面的对比,操作都是在一个数据类型上进行的,这里进行最后一个对比,一个函数对多个数据类型进行操作时,函数式和面向对象分别怎么实现。

Int String Rational
Int
String
Rational

这里要实现的是一个 add_values(x, y) 的操作,把两个数据相加,但是 x, y 可能是不同的类型的。

函数式编程

函数式编程的实现相对简单:

datatype exp =
    Int    of int
  | String of string
  | Rational of real

fun add_values (v1,v2) =
    case (v1,v2) of
				(Int i,  Int j)         => Int (i+j)
      | (Int i,  String s)      => String(Int.toString i ^ s)
      | (Int i,  Rational(j,k)) => Rational(i*k+j,k)
      | (String s,  Int i)      => String(s ^ Int.toString i) (* not commutative *)
      | (String s1, String s2)  => String(s1 ^ s2)
      | (String s,  Rational(i,j)) => String(s ^ Int.toString i ^ "/" ^ Int.toString j)
      | (Rational _, Int _)        => add_values(v2,v1)
      | (Rational(i,j), String s)  => String(Int.toString i ^ "/" ^ Int.toString j ^ s)
      | (Rational(a,b), Rational(c,d)) => Rational(a*d+b*c,b*d)
      | _ => raise BadResult "non-values passed to add_values"

这里的操作是 add_values,所以只要把所有可能的数据类型(总共9种)都列出来,就可以了。

面向对象编程:二次分派

按照上面面向对象编程的例子,我们可以这么做:

class Int < Value
	...
  def add_values v
    if v.is_a? Int
      i + v.i
    elsif v.is_a? MyString
      i.to_s + v.i
    else
      ...
    end
  end
end

class MyString < Value
  ...
end

在 add_values 这个方法里面去做判断,看传入参数的类型,去做相应的操作。这种做法不是那么的 面向对象,可以有另外一种写法:

class Int < Value
	...
  # double-dispatch for adding values
  def add_values v # first dispatch
    v.addInt self
  end
  def addInt v # second dispatch: other is Int
    Int.new(v.i + i)
  end
  def addString v # second dispatch: other is MyString (notice order flipped)
    MyString.new(v.s + i.to_s)
  end
  def addRational v # second dispatch: other is MyRational
    MyRational.new(v.i+v.j*i,v.j)
  end
end

class MyString < Value
  ...
  # double-dispatch for adding values
  def add_values v # first dispatch
    v.addString self
  end
  def addInt v # second dispatch: other is Int (notice order is flipped)
    MyString.new(v.i.to_s + s)
  end
  def addString v # second dispatch: other is MyString (notice order flipped)
    MyString.new(v.s + s)
  end
  def addRational v # second dispatch: other is MyRational (notice order flipped)
    MyString.new(v.i.to_s + "/" + v.j.to_s + s)
  end
end
...

这里涉及到了一个概念 二次分派 (Double Dispatch),在一次方法的调用过程中,做了两次 动态分派 (Dynamic Dispatch) 。用例子来说明

i = Int.new(1)
s = MyString.new("string")
i.add_values(s)

i.add_values(s)在调用这个方法时,实现了一次 dispatch,到 add_values 这个方法里后,做的其实是 s.addInt i,也就是去调用了 MyString 里的 addInt 这个方法,这是第二次 dispatch,所以叫做 double dispatch。

总结

函数式编程 和 面向对象编程 对比下来,我们并不能说哪一种模式更好。但是可以看出它们在思维上是截然不同的。函数式编程中侧重要做什么,面向对象编程侧重对象的抽象化,在有些编程语言里,比如 Java,是都可以实现的,但是要用哪种还要根据需求具体考虑。如果要了解更多 函数式编程 和 面向对象编程 的基础概念的话,可以看看之前的这三篇文章。

推荐阅读:
编程语言的一些基础概念(一):静态函数式编程
编程语言的一些基础概念(二):动态函数式编程
编程语言的一些基础概念(三):面向对象

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

函数式和面向对象编程有什么区别? 的相关文章

  • 量化涌现:信息论方法识别多变量数据中的因果涌现

    来源 集智俱乐部 作者 Fernando E Rosas Pedro A M Mediano Henrik J Jensen等 译者 潘佳栋 审校 梁金 编辑 邓一雪 导语 大量个体聚集起来 常常涌现出新的复杂结构 鸟儿聚集起来形成兼具灵活
  • 为什么程序员都喜欢安静?

    大家回顾一下上学期间 你在上晚自习想完成今天老师布置的作业 但是你的班级却非常的吵闹 跟置身在菜市场一样 你能专心完成作业吗 不受周围吵闹环境的影响吗 相信大部分的人都难以静下心来认真完成作业 有时候好不容易想到一个思路 结果旁边的人拍你一
  • 编程课程与数学的关系

    教学是人类的高级思维活动 越深入 需要的各种思维能力就越多 当思维能力不足 和别人的距离就拉开了 格物斯坦小坦克知道编程课程和数学的关系是密不可分的 小学三年级以前 数学只需要记忆力就可以了 记住一些计算规则 获得90分很容易 家长往往以成
  • C++基础之纯虚函数

    一 纯虚函数的定义 纯虚函数是一种特殊的虚函数 在许多情况下 在基类中不能对虚函数给出有意义的实现 而把它声明为纯虚函数 它的实现留给该基类的派生类去做 这就是纯虚函数的作用 C 中的纯虚函数 一般在函数名后使用 0作为此类函数的标志 前面
  • Gavin Wood Web3峰会最新演讲:波卡不是智能合约平台,而是平台的平台(全文)...

    在波卡上 每个平台都在用高性能 高效率和最优的方式做着自己擅长的事 而不必让它们的用户用底层平台的货币进行支付 从而将可定制性和灵活性提高了一个台阶 本文谨代表作者个人观点 不代表火星财经立场 该内容旨在传递更多市场信息 不构成任何投资建议
  • 每日一问:你想如何破坏单例模式?

    前言 1 单例是什么 单例模式 是一种创建型设计模式 目的是保证全局一个类只有一个实例对象 分为懒汉式和饿汉式 所谓懒汉式 类似于懒加载 需要的时候才会触发初始化实例对象 而饿汉式正好相反 项目启动 类加载的时候 就会创建初始化单例对象 1
  • Python基础语法学习之变量与赋值

    近几年Python飞速发展 开始学习Python的人群不在仅仅局限于编程开发者 许多其他行业的从业者也开始将Python作为自己的职业技能 本文仍然是针对零基础的初学者 继续学习Python的基础语法 变量与赋值 主要内容包括变量和赋值的概
  • C++ 模板简介(一)—— SFINAE

    SFINAE 类型检查 Concepts SFINAE 机制是组成 C 模板机制及类型安全的相当重要的基础 全称是 Substitution failure is not an error 大概的意思就是只要找到了可用的原型 比如函数模板
  • 【NLP】自然语言处理技术在自动生成足球比赛战报上的应用

    1 背景介绍 自动生成新闻看似是一个很成熟的技术 很多年前就有各种应用 但是深入了解后我们可以发现机器自动生成的文章一般都是复述一些数字和简单的趋势变化 所以自动生成新闻的技术广泛应用在金融 体育领域 原因就是这类报道需要基于一定的事实 而
  • java入门的第一个程序代码 hello world

    很多人说 学Java真的很难 其实 这是真的 但是高薪之所以为高薪 就是因为它比普通的活难的多 今天是我第一篇的博客 我还是想鼓励想学java技术的小伙伴一起来学 很多事只有去做了 你才能知道自己能不能成功 好了 接下来看Java入门的第一
  • python字典中如何添加键值对

    添加键值对 首先定义一个空字典 gt gt gt dic 直接对字典中不存在的key进行赋值来添加 gt gt gt dic name zhangsan gt gt gt dic name zhangsan 如果key或value都是变量也
  • 【机器学习】入门:为什么梯度下降算法这么有效?

    译者 张雨佳 人们很难真正通过数学理解东西 你只需要去习惯并使用它 约翰 冯 诺伊曼 在机器学习中 我们已经习惯了使用梯度下降法解决问题 以至于没人去质疑它为什么有效 大家经常将梯度下降法模拟为爬山 要想找到崎岖地形中的顶峰 或低谷 就必须
  • 朋友问我,程序员和非程序员的思维模式有什么区别?

    英文 https javascript plainenglish io what is the difference in thinking model between programmers and normal persons 8ff8
  • 手机知识:手机的快充技术是什么,看完本文你就懂了

    目录 1 什么是手机快充 2 目前主流的手机快充协议 2 1 PD协议 2 2 PE协议 联发科 2 3 QC协议 高通 2 4 VOOC闪充 OPPO厂商 2 5 SCP FCP闪充 华为厂商 2 6 FlashCharge闪充 Vivo
  • GIT高级使用技巧

    GIT高级使用技巧 导出GIT日志到文件 按照 lt 哈希 gt lt 作者名 gt lt 作者邮箱地址 gt lt 作者日期 gt
  • jvm之栈、堆

    1 Java Virtual Machine 人群当中 一位叫java的小伙子正向周围一众人群细数着自己取得的荣耀与辉煌 就在此时 c老头和c 老头缓步走来 看着被众人围住的java c老头感叹地对着身旁的c 说道 原以为你就可以挑起我的梁
  • ySql.Data.Types.MySqlConversionException: Unable to convert MySQL date/time value to System.DateTime

    public DateTime Createtime MySql Data Types MySqlConversionException Unable to convert MySQL date time value to System D
  • 面向对象编程---基于Javaswing的医院管理系统课设

    S2021003基于Javaswing的医院管理系统 https www bilibili com video BV1e34y1Z75L share source copy web vd source 3d18b0a7b9486f50fe7
  • 对技术行业的深度思考

    技术行业是当今世界最为热门和发展迅猛的领域之一 无论是互联网 人工智能还是区块链 技术的快速发展正在改变着我们的生活和社会 然而 我们是否真正思考过技术在我们生活中的影响和意义 本文将对技术行业展开深度思考 探讨其带来的优势与挑战 以及如何
  • Python - 字典4

    复制字典 您不能简单地通过输入 dict2 dict1 来复制一个字典 因为 dict2 只会成为 dict1 的引用 对 dict1 的更改也会自动应用于 dict2 有多种方法可以复制字典 一种方法是使用内置的 copy 方法 示例 使

随机推荐

  • Unity Vuforia(高通)AR

    Unity Vuforia制作AR软件 使用过高通AR 百度AR EasyAR 还是觉得高通的使用起来更加简易 今天就记录一下怎么使用Vuforia制作一个可识别2D图片与3D物体的AR软件 使用步骤 1 想要在unity中调用与使用Vuf
  • STL 简介,标准模板库(zt)

    STL 简介 标准模板库 ZT 作者 Scott Field这篇文章是关于C 语言的一个新的扩展 标准模板库的 Standard Template Library 也叫STL 当我第一次打算写一篇关于STL的文章的时候 我不得不承认我当时低
  • C++:Command Line Arguments

    我们平常使用的main函数返回大多数是0 而且没有参数 类似下面的例子 int main return 0 我们可以通过Command Line Arguments来给主函数设置参数 通常main函数有两个参数 第一个是用来表示参数的个数
  • minibatch kmeans+可视化(数据集中的图片在resnet网络基础上进行聚类)

    import os import numpy as np import torch import torchvision transforms as transforms from PIL import Image from torchvi
  • 一分钟安装NinJa教程(Ubuntu Linux系统)

    阅读时间30秒 1 安装re2c 10秒钟 sudo apt install re2c 2 clone ninja代码 10秒 git clone http github com ninja build ninja 3 Configure
  • transaction (1)—mysql进阶(五十七)

    前面说了当设置的buffer pool size在1个G内 则不管如何设置 buffer pool instances都是一个 当在1个G以上 mysql才支持多个instances设置 每个都有自己独立的链表 多线程的情况下互不干扰运行
  • 使用 Fiddler 抓包PC微信小程序

    想查看小程序的请求 使用wireshark捣鼓了半天还是无法解析微信小程序的HTTPS协议 于是使用Fiddler试试 Tools gt Options 重启 Fiddler 点击右边的 Filter 选项卡 然后点击 Actions gt
  • SQA在线聊天记录二:质量文化与团队管理

    SQA在线聊天记录二 质量文化与团队管理 2005 05 20 来自 CSDN管理频道 共有评论 条 发表评论 嘉宾主持Bluesky 刘总的回答确实是非常全面 作为一个独立的质量部门 和其他部门做一个项目接口 比如说做项目审计的 就不可避
  • Linux部署Java项目

    文章目录 一 启动虚拟机 二 安装java环境 三 创建java项目 一 启动虚拟机 使用RockyLinux来实现 启动后登录rockylinux sudo su 修改root用户密码 passwd 切换到客户端软件连接虚拟机 ifcon
  • 初学者pandas安装三天,遇到的各种错误给大家分享一下

    第一步 直接安装报了超时错误 直接用pip install pandas 大概率会出现超时错误 第二步 采用国内镜像服务器安装pip3 install i https pypi tuna tsinghua edu cn simple pan
  • 键盘录入、顺序结构、选择结构(if、switch)

    键盘录入 为了让程序数据更符合开发的数据 更加灵活一些 使用键盘录入数据 键盘录入格式 package com briup day5 包 import java util Scanner 导包 用于键盘录入 public class A p
  • mysql多字段排序

    数据库可以对多个字段进行排序 优先级顺序按照书写顺序 说明 对于多字段排序是这个样子的 初始数据如下 观察c1字段中相同的数据 观察上图 对于单一字段来说 可以进行排序 如下图 再增加一个字段c2 进行多字段排序 c1为升序 c2为降序 由
  • 抖音作品想要上热门得有条件和前提

    眼下 短视频正处于风口浪尖 无论是企业知名度曝光 品牌推广 亦或是产品变现 效果都十分令人眼红 想要跟上时代的发展 短视频这块大蛋糕 很有必要为自己分出一块 以时下很火的短视频平台 抖音为例 时至今日已上线4年 人人都知道玩转这个平台 发视
  • QT qDebug打印

    QT 命令行窗口16进制显示 QString Str qDebug lt lt Str sprintf result d 0x 02x n j tmp qDebug 16进制显示可以使用0x 02x
  • PyCharm中无法调包,报错ModuleNotFoundError: No module named ‘numpy‘

    PyCharm中无法调用numpy 报错ModuleNotFoundError No module named numpy 之后将解释器修改为 重新运行 成功
  • 超级黑科技代码!Python打造电脑人脸屏幕解锁神器附带接头暗号

    前言 让我的电脑认识我 我的电脑只有认识我 才配称之为我的电脑 今天 我们用Python实现高大上的人脸识别技术 Python里 简单的人脸识别有很多种方法可以实现 依赖于python胶水语言的特性 我们通过调用包可以快速准确的达成这一目的
  • Docker学习(四)——docker portainer可视化面板

    Docker学习 四 docker portainer可视化面板 1 什么是portainer 2 下载镜像 可省略 3 运行portainer容器 4 根据映射的主机端口 通过localhost 9000进行访问 4 1 设置密码 4 2
  • Kettle的下载安装教程和使用简介(内含第一个kettle转换案例)

    本文首先介绍Kettle工具的安装及基本概念 然后通过一个案例实操介绍Kettle工具的使用 本文重要的内容如下 Kettle的安装 1 Java的安装 登录Java的官网后 进入到下载页面 http www oracle com tech
  • 设置JVM选项-ElasticSearch

    设置JVM的方法 在ElasticSearch中设置JVM选项有两种方法 一种是通过jvm options 另一种是通过环境变量ES JAVA OPTS jvm options jvm options是优选选择的方法 通过tar或者zip安
  • 函数式和面向对象编程有什么区别?

    函数式编程 Functional Programming 和 面向对象编程 Object Oriented Programming 是两个主流的编程范式 他们有各自独特的闪光点 比如函数式编程的数据不可变 惰性求值 面向对象编程的继承 多态