没有类!面向对象切换到函数式编程,真的很难

2022-08-1123:10:54软件工程与架构Comments1,094 views字数 4644阅读模式
没有类!面向对象切换到函数式编程,真的很难文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

问:我听说过很多有关函数式编程的好东西,但是我很难理解。我在C ++ / Java / C#/ Javascript / etc方面有多年的经验,但这无济于事,感觉就像是从头开始学习代码。我应该从哪里开始?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

切换到FP样式确实需要改变观念。您将不再具有常规的原语,例如类,可变变量,循环等。在最初的几个月中,您将无法工作,在一些通常需要几分钟的简单事情上,您将被困上数小时或数天。这将很难,您会感到愚蠢。我们都做到了。但是点击它之后,您将获得超能力。我不认识一个人,在每天进行FP之后从FP切换回OOP。您可能会切换为不支持FP的语言,但仍然会使用FP概念来编写文字,这很有用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

在本文中,我将尝试分解一些概念,并回答在学习FP时困扰我的常见问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

  • 没有类
  • 您需要的只是一个功能
  • 不,您不能更改变量
  • 不,您不能进行“ for”循环
  • 您的代码不再是说明列表
  • 关于空值和异常
  • 函子,单子函数,应用程序?

1.没有类

问:没有类?那我该如何构造我的代码?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

原来你不需要类。就像在一个好的旧过程编程中一样,您的程序只是函数的集合,除了在FP中,这些函数必须具有某些属性(稍后讨论)并且还必须组成。您会经常听到“组成”一词,因为这是FP的核心思想之一。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

我建议不要再考虑“创建类的实例”或“调用类的方法”。您的程序将只是一堆可以相互调用的函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

没有类!面向对象切换到函数式编程,真的很难文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

> Slide from https://slidle.com/jivko/functional-programming文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

旁注:许多FP语言都有“类型类”的概念,不应与OOP对类的理解相混淆。类型类的目的是提供多态性。首先,您不必担心太多,但是如果您有兴趣,请查看本文:类型类说明。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

问:数据呢?我经常有一些数据和函数可以在一类中更改这些数据。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

为此,我们有代数数据类型(ADT),这只是保存数据的记录的奇特名称。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

您可以将其视为仅具有构造函数而没有其他任何东西的类。使用FP术语,它们是“类型”,而构造函数称为“类型构造函数”。这是构造类型并从中获取值的方式:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

请注意,在Haskell中,name和age实际上是采用Person类型的值并返回其字段的函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

问:好的,但是,例如,如何更改此人的年龄?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

改变事物的位置(从对命令性编程的理解)是一个突变,并且您不能在FP中进行突变(稍后会详细介绍)。如果要更改某些内容,请复制该副本。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

有两种ADT值得了解:产品类型和总和类型。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

  • 产品类型:字段的集合,必须指定所有字段才能构造类型:
  • 总和类型:代表可选性。您的类型是其他类型。例如,形状可以是圆形或正方形。

ADT也可以嵌套:Shape是一个求和类型,其中每个选项可以是一个求和或一个乘积。任何类型的域模型都可以表示为总和与乘积类型的组合。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

问:为什么总和和产品是如此特别?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

除了作为建模的基本构建块外,大多数FP语言都对其本身提供支持。可以对产品类型进行解构和静态检查,而总和类型可以用于模式匹配:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

2.您需要的只是一个函数

认识您的新朋友-一种函数。您可能会用不同的名称来了解它:getter,setter,构造函数,方法,生成器,静态函数等。在OOP中,这些名称与不同的上下文关联并且具有不同的属性。在FP中,函数始终只是一个函数-它以值作为输入,并以值作为输出。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

无需实例化任何函数即可使用(因为没有类),只需导入定义了函数的模块并调用它即可。如上面的Shapes示例中所示,函数程序只是ADT和函数的集合。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

函数应具有3个主要属性:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

  • 纯函数:无副作用。函数不能做超出其类型定义所说明的范围的事情。例如,接受一个Int并返回一个Int的函数不能更改全局变量,访问文件系统,执行网络请求等。它只能(仅)对输入进行一些转换并返回一些值。
  • 总计:返回所有输入的值。在某些输入上崩溃或引发异常的函数不是全部或部分。例如一个div函数:类型声明保证它接受一个Int并返回一个Int,但是如果第二个参数为0,它将抛出“被零除”的异常,因此不是总和。
  • 确定性:对于相同的输入返回相同的结果。对于确定性函数,调用时间和方式无关紧要-它始终会返回相同的值。取决于当前日期,时钟,时区或某些外部状态的功能不确定。

大多数编程语言不能静态地强制执行这些属性,因此程序员有责任满足这些属性。例如,Scala编译器将欣然接受不纯,部分和不确定的函数:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

另一方面,在Haskell中,您不能(轻松地)编写一个不是纯粹的或不确定的函数:任何一种副作用函数都将返回一个IO,IO表示“副作用”计算。Totality属性仍在程序员上,因为您可以引发异常或返回所谓的bottom,这将终止程序。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

问:为什么我要关心函数是否具有这些属性?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

如果一个函数满足这些属性,您将获得“参照透明性”(在另一篇文章中有更详细的介绍)。简而言之,您将能够查看函数类型定义,并确切知道它可以做什么和不能做什么。您可以毫不费力地重构代码,因为RT可以保证您不会出错。基本上,RT是使我们能够控制软件复杂性的原因。在OOP中进行重构可能是一场噩梦,因为在实际运行程序并在脑海中建立思维模型之前,您不知道哪些对象会调用什么以及何时调用。即使那样,这也不是一件容易的事。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

3.不,您不能更改变量

问:这是最奇怪的部分,如何在不更改变量的情况下使有用的东西?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

如果您有一个与Person(“ Bob”,42)绑定的可变人员,则不能将其重新分配给Person(“ Bob”,43)。您可以做的是通过创建一个副本并指定要更改的内容来创建其他变量(如我们之前所讨论的)。变量是不可变的,仅用于别名或标签值,而不用作物理引用或指向实际数据的指针。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

问:为什么不就地更改它?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

因为它破坏了参照透明性,并且正如我之前所说,参照透明性是FP的关键。没有可变变量,这将使您的生活变得更加轻松,这是一个公平的代价。此外,没有任何变化意味着您可以免费获得线程安全代码,没有更多的周末浪费在“仅在周二晚上发生的情况”并发错误。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

不变性是一个简单的概念,但经过多年的OOP经验,很难采用。人们通常会在Scala中恢复使用var只是为了“使此功能正常运行”。刚开始时这样做很好,但始终寻找一个不变的实现。此外,Haskell中没有这样的“ hack”,因此从第一天起就必须保持不变。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

4.不,您不能进行“ for”循环

没有突变意味着没有“ for”循环,因为它通常会突变一些计数器“ i”,直到满足某些谓词为止。但是,我们还有其他实现相同目的的方法-递归和高阶函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

递归

FP中无处不在,您必须对递归感到满意。例如,列表中所有数字的总和如下所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

通常使用递归数据结构(例如列表或树)。甚至自然数也可以用这种方式表示。遍历这些结构的自然方法是在类型构造函数上进行模式匹配,并将递归函数应用于数据结构的递归部分。一般模式是首先定义一个基本案例,例如一个空列表案例以终止递归,然后定义一个一般案例。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

高阶函数

高阶函数将其他函数作为参数。谈到迭代,您必须知道如何使用map和fold:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

问:名字怎么了?map?不像foreach吗?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

是的,但仅适用于列表。很快,您将发现map并不是要转换列表,而是根据我们要映射的内容具有不同的语义。如果您想了解更多信息-查找Functor,这是一种提供映射接口的高级类型。但是不必太担心Functors,只需将map视为知道如何迭代数据结构(例如列表,树,字典等)的函数即可。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

fold也具有更深的含义,并且与Foldable有关。直觉是,它需要一些数据结构并产生单个值,例如sum。请注意,与fold不同,map将函数独立地应用于每个值,而fold可以携带某种取决于先前值的累加器。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

还有更多的功能,但是了解这两个功能可以使您对大多数迭代问题大有帮助。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

5.您的代码不再是说明列表

用命令式语言可以执行以下操作:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

这些功能具有“副作用”,例如他们做某事。他们采取行动的结果是整个程序的状态发生了变化-一些文件已写入磁盘,在控制台中输出,更新的内部实体映射等。调用此函数-完成,完成,执行。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

好吧,这里没有新内容,这是我通常编程的方式。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

可以,但是在功能程序中,直到最后一刻才执行任何操作。您的函数必须采用值并返回值,不允许有副作用。一个功能的输出是其他功能的输入,而其他功能又为其他功能创建输入,依此类推。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

该程序在FP中的外观如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

请注意unsafeRun函数(假设其由语言提供)。在执行unsafeRun之前,我们要做的只是将功能粘合在一起,什么也不会执行。我们正在制定某种执行计划-“必须先调用此函数,然后根据其输出,我们将调用这两个函数之一”,依此类推。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

这也不是一个容易理解的概念,因为我们习惯于在此处抛出一些其他行为来执行某些操作,例如记录语句或设置一些标志,清除队列等。由于这些额外的行为,您再也无法摆脱函数必须遵循类型并与其他函数组合。这是一件好事–它迫使我们对程序的工作原理有所了解,并确保所有内容都在函数的类型签名中进行了编码。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

6.关于空值和异常

空值都是命令式编写的代码库。null的问题在于,它是一种较低级别的抽象,泄漏到了较高级别的系统中。如果我看到一个返回Person的函数,那么(如果一个函数是合计的)我希望得到一个具有名称,地址等的Person。null不是一个人。null通常用于表示缺失或某种内部故障,这些故障会阻止函数返回正确的值。如果某个函数无法以某种方式返回Person,则应在其类型定义中这样说。在FP中,我们可以用求和类型表示缺勤情况:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

如果函数返回Maybe或Person的anOption,则它明确表示-不保证Person。调用方将必须检查返回的值是Some还是None,这意味着不再有null取消引用问题或null指针异常。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

如果您考虑一下,null是一种与运行时系统而不是您的程序逻辑相关的低级原语。当您使用带有垃圾回收的高级语言编写代码时,您实际上并不关心对象在内存中的分配时间和方式,也不关心函数生成的机器代码是什么。这就是高级语言的用途-它们创建了一个抽象,因此您无需考虑细节。null破坏了这种抽象,因此代码被奇怪的p!= null检查甚至更糟的—引用问题所污染。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

同样,例外。不需要仅具有特殊语法的特殊机制即可处理特殊情况。在您的纯程序中,可以用普通值表示缺勤,失败和异常。带有throw e的throwing异常会使函数成为部分函数(非总计),这又再次破坏了引用透明性并产生了问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

如果您使用JVM并使用Java库,则必须处理异常。在某些特殊情况下,例如IO,可以使用异常,但要确保它是函数类型的一部分-调用者必须知道函数抛出,可以抛出哪种异常以及可以在编译时检查这些承诺。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

7.函子,单子函数,应用程序?

问:我听说FP人士经常谈论这些事情,但对我来说毫无意义。有简单的解释吗?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

人们发现了一般模式,并从类别理论中给它们起了名字。Functor,Monads和Traversables是非常强大且通用的抽象,您将在各处看到它们。它本身可能就是文章的主题。但是现在-不用担心。您最终将了解它们(甚至可以自己重新发明它们)。熟悉函数组成,高阶函数和多态函数。然后阅读有关类型类的信息。之后,函子和Monad应自然而然地出现。这里的要点是,没有魔术,而且没有比我们在本文中已经讨论的更多的东西了-纯函数和函数组成。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

希望对您有所帮助,如果没有,请给我您的反馈。就像有人说的:“一旦您了解Monad,便失去了向他人解释的能力”,所以我希望本文与OOP开发人员通常所经历的相距不远。感谢您阅读并享受您的FP旅程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/arc/27017.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/arc/27017.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定