功能迅捷

什么将“ currying”,“ monads”,“代数数据类型”组合在一起? 不仅一些开发人员试图绕过这些词汇,而且功能编程也是如此。 在叶夫根尼·埃尔切夫(Yevgeny Elchev)的精心指导下,我们陷入了一种功能范式,几乎了解了一切。 不要害怕,请随时阅读第十版的AppsCast播客的笔录



丹尼尔·波波夫(Daniil Popov):大家好。 今天,我们的客人是阳光灿烂的克拉斯诺亚尔斯克(Krasnoyarsk)的叶夫根尼·埃尔切夫(Evgeny Elchev)。 尤金,告诉我您在做什么以及如何进行函数式编程?

Evgeny Elchev:大家好。 我是Redmadrobot的iOS开发人员,和其他所有人一样,我绘制按钮,有时我编写业务逻辑。

我首先通过文章熟悉了函数式编程。 我不太了解这一点,认为这是一种过程编程,没有类。 当我更仔细地阅读其中一篇文章时,我意识到自己错了,因此开始深入研究。 这并不是说我刚开始使用函数式编程,因为真正的追随者会尽力而为,并在可能的情况下使用monads在Haskell中进行编写。 我只是投入,仅在生产中使用它。

丹尼尔·波波夫(Daniil Popov):所以,这些单子已经走了。

叶夫根尼·埃尔切夫(Evgeny Elchev):已经很难了吗?

丹尼尔·波波夫(Daniil Popov):我尝试以同样的方式进行操作,但是我打开了这篇文章,看到“ currying”,“ monad”一词,然后立即将其关闭,以为我还不值得。 我有机会吗?

叶夫根尼·埃尔切夫(Evgeny Elchev):当然。 您可能一点都不知道。

简而言之,功能主义


丹尼尔·波波夫(Daniil Popov):让我们为从未听说过功能范例的人提供一个简单的定义。

Evgeny Elchev:每个人都以自己的方式理解范例。 如果我们从Wikipedia进行解释,那就是使用数学函数,其中整个程序都被解释为数学函数。

函数方法(FP)是在工作中使用仅具有输入参数和输出值的函数时。 如果整个程序都包含这些功能,则这是一个功能程序。

Daniil Popov: OOP是常规过程编程的逻辑延续,解决了将数据封装在类中的问题。 函数式编程应解决哪些问题?

叶夫根尼·埃尔切夫(Evgeny Elchev):数学家发明了函数式编程。 伙计们聚集在一起,决定创建一个可以证明一切的范例。 有代码,尚未发布,但我们将证明所有内容。 可以计算程序的任何一点,了解我们允许采取某些行动时将要到的地方。

听起来很抽象,所以让我们看一个纯函数的例子。 我们编写了一个带两个参数的sum函数,将2和3传递给它,得到5,我们可以证明这一点。 总是如此。 如果我们的整个程序都包含这样的功能,那么一切都是可以证明的。

创建语言时,基本功能开始被遗漏,并且出现了其他功能:lambda,高阶函数,monad,monoid。

功能范例不能解决单个问题,同样的愿望是编写尽可能简单的优质代码,以使程序稳定且易于维护。

如果仔细观察,我们在OOP中使用的许多功能都会反映在功能方法中。 OPP中有一些类封装了一组字段。 在FP中,这也可以使用类型类来完成。 正如Vitaly Bragilevsky喜欢说的那样 :“如果您看一下平板电脑上的数据沿线和功能列行,那么FI沿列行,OOP沿行行。” 仅此而已。

Daniil Popov: FI与其他范例有何关系? 我可以用OOP编写功能吗? 如何混合范例,这有意义吗?

叶夫根尼·埃尔切夫(Evgeny Elchev):范例仅限于您使用数据编写函数的事实。 AF的特征之一是没有可变状态。 如果您的数据是一类,则没有问题。 如果该类是完全不变的,则可以使用它。 类只是一个类型,如字符串或数字,只是更复杂,由多个值组成。

丹尼尔·波波夫(Daniil Popov):您之前曾说过,如果仅编写功能性程序,就可以证明程序的数学正确性。 然后,针对功能语言的“编译的作品”笑话就不再是笑话了,对吧?

叶夫根尼·埃尔切夫(Evgeny Elchev):如果您查看I / O错误,则可以。 以前,程序员一直在为这个问题而苦苦挣扎:连接到网络,没有网络,没有任何回报,一切都崩溃了。 对于该解决方案,最简单的方法是检查结果-nill / not nill,但是由于存在未将所有因素都考虑在内的风险,因此程序可能会编译并崩溃。

在现代语言中,这是决定的。 在Haskell中,您可以编写一个可以运行且不会崩溃的程序,但是没有人会说出它如何正确运行。 当然,有严格的类型,您不能通过在字符串中添加数字来犯错,但是您始终可以在应用程序中留下错误,它会起作用。

功能方法在Swift中的位置


Alexei Kudryavtsev: Swift可以被称为一种功能语言吗?

叶夫根尼·埃尔切夫(Evgeny Elchev):有可能。 功能被定位为无状态,但是您可以使用Swift编写避免此类状态的功能。 同时,Swift与在iOS上编写不同,iOS上到处都有状态。 当然,在Swift中没有像Haskell这样的特殊指令,默认情况下所有函数都是干净的,并且编译器将不允许您访问状态并进行更改。 如果将功能标记为“脏”,则更改将变为可用。

Alexei Kudryavtsev:在第二个或第三个Swift中,有一个纯修饰符,但它仅在编译级别起作用,因此全局值不会更改。 您在其中编写了一些内容,但编译器将所有内容删减了。

Evgeny Elchev:是的,在iOS中,编译器将不会遵循此规则。 一切都完全取决于我们的良心:正如您所写的那样。

Alexei Kudryavtsev:您说的是iOS应用程序中有很多状态,但是如果您以功能性风格编写,则在何处以及如何处理它们?

Evgeny Elchev:最重要的状态是UI,例如输入字段。 实际上,他们什么也做不了。 您可以尝试从中抽象出来,放在一个地方收集并编写尽可能多的代码,而无需考虑它们。 例如,您编写了一个肮脏的函数,该函数从UI中获取所有数据。

在我的文章中,我给出了一个授权表单的示例,其中用户输入用户名/密码很重要。 我们编写了一个脏函数,该函数返回带有授权数据的结构,然后在其上编写干净的代码。 我们获得了经过验证的数据,如果结果有效,则向服务器发送请求。 服务器请求也是一个肮脏的功能,并且完全处理它可能是干净的。 “接收,解析”是一个线性函数:数据的输入,输出是我们的结构。 然后将其转换,过滤并可以再次显示在屏幕上。

Alexei Kudryavtsev :在Haskell中,编译器有很大帮助。 如果状态来自某个地方,则整个呼叫链都将被视为肮脏的,您需要将所有内容包装在monad中。 如果函数是纯函数,则结果的缓存有效-相同的输出始终是相同的输出。 在Swift中,您必须自己实现地图,并尝试返回结果(如果已缓存)。

丹尼尔·波波夫(Daniil Popov):大多数现代语言被认为是多范式的,许多具有功能性。 例如,在Java中,接口有一个特殊的注释- @FunctionalInterface ,它强制开发人员在接口中仅定义一个方法,以便在整个代码中都使用lambdas形式的该接口。 当您添加第二种方法或删除现有方法时,编译器将开始发誓它已不再是功能性接口。 除iOS平台之外,Swift是否具有这样的功能?

Evgeny Elchev:我很难理解这样的注释在Java中的作用。 如果您是要实现该类的接口,然后仅实现一个方法,则Swift中没有此类限制。 您可以创建typealias,对其进行命名,并将其用作函数类型,参数类型和变量类型,以分配闭包。 您可以定义约束-输入和输出闭包参数。 可以使用闭包的高阶函数本身就是多态性,在Swift中,您可以在类型上建立多态性,而不仅限于对象。

但是我不知道具体的功能。 在第一个Swift中曾经使用过curry,但是它被剪掉了。 现在,我们可以编写一个用于自我介绍的函数,或者编写一个函数以使其相互返回闭包,但这并不完全正确。

我们没有盒装仿函数或单子函数。 他们甚至都不能写。 Swift 5.1中的新功能应该可以帮助实现这一点,但是我试图编写这样的代码,而xCode失败了。

原则上,在Swift中,您可以根据自己的意愿轻松地完成所有操作。 开箱即用已经有一个可选的monad(也许在Haskell中)。 她具有用于构建线性计算的地图和平面图。

Swift具有强大的模式匹配功能。 开关几乎以每种语言存在,并且在大多数情况下,将整数与单位关联,可以将变量与特定的模式,范围,类型关联,并从相关类型中提取值。 有迦太基-您要编写一个新类型,将其他几个类型传递给它。 基于它们,您还可以进行模式匹配。 有一个可以限制类型,将相关类型绑定到它们的枚举。

Alexei Kudryavtsev:我要澄清一下,相关类型与Kotlin密封类相似。 这是您可以在其中放入绑定值的情况下的枚举。 在switch中,您可以编写:在这种情况下,在对象内部展开。 例如,带有对应对象的用户案例和公司案例可以被枚举并可以切换。 只有密封的类是可扩展的,而switch是有限的。

动员者为什么需要功能主义?


Daniil Popov:一种功能方法对移动开发有何帮助? 他有解决什么问题吗?

叶夫根尼·埃尔切夫(Evgeny Elchev):在函数式编程的帮助下,没有特定的问题可以精确解决。

最重要的是,即使无法解决这些原则,我们也必须放弃条件,因为它们是主要的痛苦。

通过放弃它们,您可以使代码更易于理解。 我并不是说错误会更少,因为至少必须对此进行衡量。 但是,当您开始实现某些功能时,代码会更改。 通常情况下,您会查看代码,并且其中的所有内容都是如此,但是您开始重写,交换,删除不必要的内容并更易于阅读。

遵循功能范式,您将获得其他启发。

丹尼尔·波波夫(Daniil Popov):如果我开始用OOP语言编写此类不可变的类并使用不可变方法,是否可以说我在功能上进行编写?

叶夫根尼·埃尔切夫(Evgeny Elchev):是的,当您开始看到专业人士时。 由于缺少全局状态,测试方法变得越来越容易,从方法组成计算链变得更加容易。

Daniil Popov:在您的文章中,您将解释什么是纯功能和副作用。 您以求和为例,该函数还修改了外部状态。 问题是,当您阅读此类代码时,很难将所有更改都掌握在头脑中:您需要查看此全局变量,其他人可以读取该全局变量,其他人可以将其写入可能发生的情况。 但是功能性方法允许您停留在流中,而不必进入相邻的类,只需阅读代码即可。

Alexei Kudryavtsev:如果您使用的是功能语言,那么一方面可以简化代码编写,但是另一方面,您必须了解您现在所使用的monad。

叶夫根尼·埃尔切夫(Evgeny Elchev):是的,但是当您开始用纯函数编写所有内容时,还会出现其他问题。 例如,如何构建一长串计算。 在通常的样式中,无需考虑它,就可以轻松转储最初不存在的数据。 在功能方法中,这是无法完成的:您必须断开链,将几种方法中使用的所有计算都连接到状态。 您需要习惯它。

另一方面,与OPP中的类会使代码变得僵化且难以编写的函数不同,函数可以更加灵活。 您可以编写一个函数,在闭包的帮助下增加自由度,抛出此类函数并将它们组合成链。

Alexei Kudryavtsev:这与Unix意识形态相似:有bash,terminal,您可以将数据从执行一个小动作的小程序传输到其他程序。

丹尼尔·波波夫(Daniil Popov):这让我想起了Rx方法,他们在那里编写巨型链。

叶夫根尼·埃尔切夫(Evgeny Elchev):你们都是对的。 而Unix-way就是这样,而Rx是绑定和反应性思想的融合。 在FP中,我们绑定到事件的源,并在计算链中更改它,将结果绑定到最终状态。

丹尼尔·波波夫(Daniil Popov):多范式语言到底有没有优势,它可以做到这一点多么方便和有用?

叶夫根尼·埃尔切夫(Evgeny Elchev):如果严格遵循某种范例,总会有一些不便之处。 有一些功能上很难实现的事情,例如,存储状态和创建缓存。

当可以选择更适合特定任务的工具时,这很酷。

您可以创建一个类,在其内部以功能样式创建多个方法,并简洁地将代码链式组织起来,或者完全放弃该类,制作必要的功能并使用它们。

不利之处在于选择的困境,选择越多,选择就越困难。 它也变得越来越难以理解:选项越多,代码读取就越困难。

关于莫纳德果酱


Alexei Kudryavtsev:回到功能主义,什么是单子论?

叶夫根尼·埃尔切夫(Evgeny Elchev):我将其称为一个容器,您可以在其中组合计算链。 最简单的方法是一个容器,可以向其应用功能并将其转换为具有修改后值的新容器。

想象一下草莓所在的盒子,其中有一个设备可以让您用草莓做果酱,但是您无法在其中放一盒草莓,需要将其倒出。 Monads-这就是使您可以在设备中放入盒子的东西。

这不是直接意义上的状态,因为状态是单独存储的,但是这里是带有值的上下文(框),您会从一个传递到另一个。 这是信息从一种计算到另一种计算的转移。

Alexei Kudryavtsev:事实证明,在实用的方法中,要制作果酱,您需要进入包装盒内...

叶夫根尼·埃尔切夫(Evgeny Elchev):美丽之处在于您不必爬进箱子。 您可以扔一个盒子。

精英的功能?


丹尼尔·波波夫(Daniil Popov):有一种观点认为,如果没有数学博士学位,就无法进行函数式编程。 这是真的吗

Evgeny Elchev:这是不正确的。 当然,数学知识可以使一切变得更好,但是我在毕业后忘记了数学并且过着正常的生活。 实际上,所有这些都是解决特定问题的语言所体现的工具。 可以使用它们而无需尝试进行数学证明。 从数学的角度编译方程式时,通过键入抛出几行代码会更快,更轻松,并且它们会起作用。

Alexei Kudryavtsev:功能方法的爱好会在多大程度上干扰产品开发? 如果部分代码已经按功能编写,那么使用它有任何困难吗?

Evgeny Elchev:一点也不。 如果您不是狂人,并且不会编写带有装饰器的大型生态系统,则可以使用相同的模式匹配。

如果要切换到功能主义的新元素,将会更加困难。 例如,最近出现了第五个Swift和结果monad,您以前从未使用过它,但是现在您决定将所有东西都放在上面。 您将查询功能带到网络上,并写出它的结果现在是结果(数据还是错误),然后决定与下一个查询合并,然后在其中单独包含值和错误,并且需要重写它。 我开始在一个地方这样写,两天后醒来,当我重写一半的代码时,我还为库制作了新的包装器,以实现精美的集成。

从哪里开始?


丹尼尔·波波夫(Daniil Popov):初学者应该读什么以理解函数式编程?

叶夫根尼·埃尔切夫(Evgeny Elchev):我们需要采用一种纯粹的功能语言,例如Haskell,并在实践中进行尝试。 您拿一本教科书,然后做一些最简单的例子。 在这里,您将了解这种方法-如果没有针对性,则无法创建可以更改值的变量。 我个人曾经读过一本书“以善为名学习Haskell”,其中所有内容都用简单的语言描述。 之后,您可以开始在Internet上阅读文章:有关Monad在Swift中的外观,以及代数数据类型。 有几篇文章,很显然,这不应该害怕。

丹尼尔·波波夫(Daniil Popov) :最困难的事情是打破自己的头脑。

叶夫根尼·埃尔切夫(Evgeny Elchev):无需深入研究函数式编程。 许多人认为他们都会坐下来并开始编写功能文件-这是错误的。

Alexei Kudryavtsev:我看到的最酷的东西是Denis Moskvin的Haskell的Stepic课程 。 首先将两个数字相加,最后将单子包装成单子。 而且,如果您想完全打定主意,那就是《计算机程序解释的结构》这本书是Lisp中的一门课程,从简单的示例到您在Lisp中编写Lisp解释器的过程。

如果对功能主义的主要恐惧已经过去,那么请查看春季AppsConf 上Vitaliy Bragilevsky报告 。 但是,在AppsConf的秋季,我们将探讨一些同样有趣的话题-iOS社区期待Daniil Goncharov撰写的有关蓝牙逆向工程报告 ,Android开发人员将与Alexander Smirnov一起讨论构建动画的当前方法。

Source: https://habr.com/ru/post/zh-CN462121/


All Articles