Tag: 函数式编程

读者进行环境注入

好吧,想象一下那些为孩子们准备的贴纸书 ,其中有一些孩子可以放在不同背景下的贴纸。 例如,他可以拿牛贴纸,并贴在田野中,山峦和河流在流淌。 或者,他可以将牛放在实验实验室,空间或马戏团中……这本书提供了许多不同的景观供您选择。 结果是一个场景,其中母牛在田野,马戏团或太空中嚼嚼嚼的东西,尽管那里可能没有很多草可以咀嚼…… 孩子首先从书中的贴纸页面上拿走贴纸,然后决定将其放置在哪里(大多数情况下遵循完全随机的逻辑),最后将其放置在特定的环境中 。 然后他可以再贴上一张贴纸,一头母牛在做别的事情(跳跃,睡觉等等),或者另一头人物在做什么。 你明白了。 注入上下文 好吧,回到成年人的世界,那里不再有贴纸玩(除非有孩子)。 假设您正在构建一个应用程序,并且在某个时候您从存储库中获取了一些数据。 它可以来自任何来源,网络或本地,都没有关系。 假设您正在构建社交应用程序,并且需要为特定的电子邮件吸引用户。 您将拥有一个带有以下签名的方法: func getUser(email:String)->用户 在此示例中,按照我们的不干胶标签书类似, 不干胶标签是电子邮件, 结果是User对象。 但是实际情况在哪里? 景观是发生所有这些事情的背景,在贴纸书中是马戏团或田野。 在我们的小示例中,它可能是我们正在访问的特定存储库以获取数据: Web服务 ,成熟的持久层或测试模拟(其中可能没有任何东西可嚼)。 对于我们的应用程序,我们称其为服务 。 那么,我们如何让上一个函数知道要使用哪个服务,即应该从哪里获取数据呢? 换句话说,我们如何注入服务? 直接的解决方案可能只是将另一个参数传递给函数。 func getUser(email:String,service:Service)->用户{ 返回service.findUser(withEmail:email) } 现在,该服务访问存储库,以返回用户以传递我们传递的电子邮件。 目前,我们正在忽略各种错误。 这非常好,因为现在出于测试目的,我们可以用其他符合相同协议的东西来代替服务。 尽管这种方法没有错,但是这意味着现在这种类型的每个函数都必须包含服务参数。 向服务参数添加默认值可能看起来更简单,您可以再次使用以前的函数签名,该服务将成为默认服务。 但是,随后您使对服务的依赖关系变得隐含了 ,API用户很难看到它。 还有另一个问题,可能很难看到。 使用这种语法,函数的调用者将必须传递userId和服务,换句话说,在进行调用时必须知道该信息。 乍看之下,两条信息是完全不相关的:用户的ID和获取该用户的服务。 对于此函数的调用者,我们正在混合两种完全不同的职责 :业务逻辑和持久性或网络代码。 替代。 读者 您是一名优秀的程序员,因此您希望将职责分开。 您相信SOLID中的“ S”,因此一类必须只有一个改变的理由 。 我们需要改进该代码。 如果还有另一种方式怎么办? 如果我们可以从函数中删除service参数,并获得允许部分应用的结果值,该怎么办? […]

‘功能性’Swift#5:JSON提取和解析

在前面的部分(1、2、3、4)中,我们已经看到了如何将一系列保护语句重写为简单的函数链,并将其扩展为返回Result类型。 在本文中,我们将使用相同的技术(使用可选版本)来获取和解析JSON文件。 首先,我们需要JSON: [ { “ title”:“ Title1”, “字幕”:“ Subtitle1” }, { “ title”:“ Title2”, “字幕”:“ Subtitle2” } ] …以及解析为…的类型 结构项:可编码 { var标题:字符串 var副标题:字串 } 请注意,该结构被标记为符合Swift 4 Codable协议。 现在,让我们为文件路径提供一个帮助程序类型(并非绝对必要,但我认为它使我们正在处理的类型更加明确): 结构文件路径 { private(set)var值:字符串 init?(在捆绑包中:Bundle = .main, ofType fileExt:字符串=“ json”, 命名名称:字符串?) { 守卫让路径= bundle.path(forResource:name, ofType:fileExt) 否则{return nil} self.value =路径 } } 现在我们可以构建我们的展开函数: 结构展开 { typealias StringToData =(字符串)->数据? […]

使用结果扩展编写声明性代码

最近,我读了Benedikt Terhechte的精彩文章,他在其中扩展了optionals的API。 在标准银行,我们非常广泛地使用结果类型,因此我采用了相同的想法并将其应用于结果类型。 在这篇文章之后,我们将获得可组合的代码和声明性的API,类似于下面的代码片段。 您可以在以下Playground文件中找到一个工作示例。 TYRONEMICHAEL /结果扩展 result-extensions –使用结果扩展 github.com 编写声明性代码 虚构的摘要 假设我们正在为客户构建一个简单的应用程序。 与域名专家坐在一起后,他通知我们任何人都可以浏览该应用程序,但是要成为注册用户,您需要满足以下要求: 注册用户必须具有有效的 标识符 。 注册用户必须具有有效的电子邮件地址,并且该电子邮件地址必须是Gmail地址。 注册用户必须是千禧一代或X一代 。 我们需要向用户展示为什么尝试后无法注册 。 用应用程序扩展结果类型 要求之一是向用户显示为什么他们不能成为注册用户。 通过使用应用程序,我们可以非常漂亮地实现这一目标。 在编写我们的应用程序之前,我们需要定义一个称为半组的协议。 布兰登·威廉姆斯(Brandon Williams)对半群是什么以及为什么要使用半群有很好的解释。 出于本文的目的,我们只需要一个Array即可符合协议。 现在,我们可以编写扩展结果类型的应用程序。 这允许编写以下代码。 如果在上面打印用户对象的内容,将得到以下内容: 失败:3个要素 -“无效的ID” – “不合规电邮” -“无效的千禧一代” 斯蒂芬·塞利斯(Stephen Celis)的精彩视频详细介绍了上述应用程序和Swift。 一块很棒的手表。 用AND扩展结果类型 要求之一是用户必须具有有效的电子邮件地址和有效的Gmail地址。 让我们使用和的以下代码扩展结果类型。 现在,我们可以执行以下操作。 如果打印结果,将得到以下输出。 失败:1个元素 -“无效的Gmail地址” 用OR扩展结果类型 最后一个要求是用户是千禧一代或X代。我们可以通过使用or扩展结果类型来满足此要求。 现在我们有以下内容。 如果打印结果,将得到以下输出。 失败:2个要素 -“无效的千禧一代” -“无效的gen x” […]

Swift已经具有结果类型

也许。 在过去的几年中,我有幸使用多种语言编写和部署应用程序。 Scala,ruby,javascript,haskell,clojure等。每次我拿起一个工具时,特别是如果它对我来说是新的,我都会尝试学习它的精髓。 编程语言是任何人都可以用来构建事物的思想和概念的工具箱。 但是,我对同事和在线使用当前可用工具的不满之处感到非常不满。 Rob Napier谈论了快速不是功能语言的话题,您应该注意一下。 这是关于如何使用一种语言的方式,而不是人们希望的方式的一课。 但是,我真的很失望Rob提出swift应该添加一个Result枚举。 Swift已经具有此功能,但是我想很多人都不喜欢它。 Swift具有Optional类型,但通常不会直接看到它。 您通过语言构造与可选元素进行交互。 一个例子: 在上面的示例中找不到“可选”,“一些”和“无”的字样。 Optional基本上是一种“也许”类型,直接在其语言中获得支持真的很好。 无需编写一堆case .some和其他类似的语句,我们只需使用let和其他语言结构来检测其中是否有任何东西。 下面的示例类似于针对Result类型看到的许多示例: 乍一看,这似乎很好。 我在许多快速项目中都添加了这样的枚举。 但是,最近我注意到,我总是不得不执行大量的仪式来从枚举中提取值: 或者可以尝试仅提取并使用成功案例: 为了与语言中已经存在的工具作斗争,需要进行大量工作。 Swift已经可以将函数标记为返回两种类型之一,一种是成功,另一种是Error ,并且它是throws 。 现在,我们可以使用语言的内置功能来调用该函数: 我已经说过很多人说他们避免throws因为他们认为使用异常进行控制流是不好的。 我同意,大多数情况下例外对于控制流是不合理的。 throw不会引发异常。 从迅速的文档: Swift为运行时引发,捕获,传播和操作可恢复错误提供了一流的支持。 错误不是例外情况。 可恢复的错误是预期的,应该对此进行计划。 标记为throws的函数返回Error类型为失败的结果,并且编译器强制程序员处理成功和错误情况。 do和catch就像专门的switch和case关键字,用于函数何时返回成功或错误值并提供人们期望的模式匹配功能。 是的,有rethrows以在函数堆栈中传播错误。 我在scala中做了完全相同的事情,以便在诸如Web请求之类的过程中“顺流”处理Result的错误。 这是使用类型和模式匹配的控制流,我很高兴它已内置在语言中,而不必每次都自己构建。 “ try ”一词是解释我要完成的任务的完美方法: 尝试一下,如果无法解决,我将以其他方式处理。 尝试/捕获情况的开始不是我最喜欢的单词,但我想不到更好的方法了。 我不是敏捷专家,我每天都不会使用它,我想听听您对这个问题的看法。 还是应该迅速添加一个Result枚举,并让程序员在throws和Result之间进行选择? 还是某些片段库会一直枚举而其他枚举却全部枚举的碎片库? 谢谢阅读。

Monad Menagerie

在上一篇文章中,我花了一些时间看一下Monads —数据结构提供了称为bind或flatMap (或>>>= )的功能。 Monads为我们提供了一种强大的新功能组合方式。 然后,我花了一些时间来尝试了解Monad是什么。 最后,我们看到Optionals , Arrays和WebData类型都是Monad(并为它们定义了bind )。 在本文中,我将介绍很多基础知识,看看一些我们在Swift中不经常看到的Monad(或至少不经常被视为Monad)的Monad示例: Readers , Writer和Futures。 所有示例都可以在Github上找到。 有一个游乐场可以尝试。 请注意,为了使本文中的代码示例简短,我漏掉了一些细节-您可以在Github中查看工作代码。 作家✍️ 让我们开始与作家。 Writer可以将一个值与有关该值的一些信息打包在一起; 随着我们在后续计算步骤中继续处理该值,我们可以继续添加信息。 它通常用作记录进度或跟踪运行总计的一种方式。 在许多地方,您会使用一个突变状态(例如,一个inout参数,对全局日志记录功能的写入,甚至只是一个print语句),但是这些操作很难测试,并且在多线程环境中无法很好地工作。 Writer有两个类型参数:第一个是“登录”或“累积”的类型; 第二个是计算的当前值。 换句话说,我们有: Writer V (计算值)可以是任何类型,但是A (累加器)必须是称为Monoid的特殊类型。 Monoids¹只是带有“加法”运算符的类型,表示为和“零”或“空”值。 我不会在本文中介绍Monoid(请查看github代码以获取更多信息),但请注意Array和Int都是Monoid。 Int的“零”当然是0 ; 而“加法”运算符当然是+ 。 更有趣的是,Array的“零”为[] ,加法为.append或+ 。 因此,举例来说,假设我们正在编写一个图形包来处理图片。 struct Picture { let pixels: [UInt8] … } 我们希望保留所有操作的记录。 为了保持跟踪而不改变状态,我们需要将记录传递到每个操作函数中,如下所示: func makeGreyScale( log: [String] ) […]

“功能性” Swift#2:使用运算符进行合成

直接从上一篇文章开始,我们现在将开始更深入地研究功能性内容。 这是我们最后一次到达的地方: 让stringToURL:(String)-> URL? = {URL(string:$ 0)} 让urlToData:(URL)->数据? = {试试? Data(contentsOf:$ 0)} 让dataToImage:(数据)-> UIImage? = {UIImage(data:$ 0)} 让stringToImage:(String?)-> UIImage? = { $ 0.flatMap(stringToURL).flatMap(urlToData).flatMap(dataToImage) } _ = stringToImage(“ https://placebear.com/200/300”) 在可重用性方面,这是对原始版本的巨大改进,但我们可以走得更远。 在每个调用中都会重复flatMap调用,因此我们可以将它们归为一组吗? 让stringToURL:(String)-> URL? = {URL(string:$ 0)} 让urlToData:(URL)->数据? = {试试? Data(contentsOf:$ 0)} 让dataToImage:(数据)-> NSImage? = {NSImage(data:$ 0)} func fMap (_ fnc:@转义(T)-> U?)->(T?)-> U? { 返回{$ 0.flatMap(fnc)} } […]

Swift地图之外的世界

在这篇文章中,我将谈论一些我们在Swift中很少听到的东西: 应用函子。 这些使我们可以用最少的代码执行一些非常强大的操作。 这使用的是我上次介绍的HKT(高级类型)框架的扩展和简化版本。 我们看过Swift中的Functors : 总而言之 , Functor是“容器”(例如Array或LinkedList之类的序列),它们具有一个称为fmap的超能力映射 ,该映射可转换容器的内容,但使容器本身保持完整。 即使您从LinkedList中的数据开始,常规映射也会始终返回Array。 在本文中,我们将进一步介绍这个想法。 fmap (和标准库中的普通地图 )在某些方面非常有用 ,但肯定会受到限制。 例如,当您想使用具有多个参数的函数来转换容器时会发生什么? 这是我们可以使用应用函子解决的问题。 守则 和以前一样,代码可以在游乐场(包括一些挑战!)和XCode项目中使用,以添加到自己的代码中。 污染数据 通过一个激励性的示例,假设我们正在编写一个Web应用程序来计算退休储蓄。 由于这是公共应用程序,因此我们担心我们可能会传递恶意数据。 处理此问题的一种方法是将所有外部Web数据视为已污染 (就像Ruby一样)。 然后,我们就有一个干净的过程来检查数据并将其标记为安全 。 在Swift中,让我们使用枚举为WebData建模: (因为我们正在使用上一篇文章中的HKT框架,所以存在可构造的一致性和TypeParameter ) 有趣的是,受污染的数据无处不在-任何一个输入被污染的函数都会有一个受污染的输出(或者等效地,只有所有输入都是安全的,函数的输出才是安全的) 因此,假设我们有一个普通函数,该函数接受3个输入并计算一个输出。 接下来,我们从网络上获得此功能的3个输入: 我们要做的是使用这些输入来运行退休金计算; 但返回一个WebData值,该值仅在所有输入都安全的情况下才是安全的 。 这是一种实现方法,其中包含一个很大的switch语句。 这行得通,但是有很大的缺点:它的功能很长(因为有3个参数,所以要在开关中测试2³= 8个组合); 我必须专门为WebData编写函数(而原始函数仅使用常规的Int,Double等); 我将对我要在应用程序中使用的每个功能重复练习。 但是, WebData是一个可应用的函子 -我们将在稍后看到它的含义-因此,我们可以免费使用HKT框架(或多或少)提供的一些帮助程序功能。 在这种情况下,我们将使用框架中的Appl3 , 将3个参数的函数应用于3个应用函子的列表。 这样,我只需一行代码即可获得与长switch语句相同的效果, 而不必更改我的原始功能。 假设我们现在清除一些数据: 对于那些喜欢“延续风格”的人,您也可以使用它,它允许具有任意数量的参数的功能: 如您所见,这比您自己解包WebData容器要简单得多。 这也是可以用于许多其他类型的技术。 稍后我们将看到一些示例。 但是首先,让我们更详细地解释应用函子。 […]

flatMap方法的实际应用— Swift 3

这一次,我们将讨论flatMap方法。 但是首先让我们考虑一下上下文。 假设我们有一个数组数组: var arrayOfArrays = [ [1, 1], [2, 2], [3, 3] ] 将其内容乘以2是一个挑战,其结果应如下所示: // [[2,2],[4,4],[6,6]] 解决此难题的一种方法可能是两次使用map Array的方法,如下所示: //首先迭代数组的数组 arrayOfArrays。 映射 {数组在 //和第二次迭代 //当前数组 返回数组。 映射 {中的整数 //将其内容乘以2 返回整数* 2 } } // [[2,2],[4,4],[6,6]] 好的,接下来是一个新的挑战,将乘法数组转换为仅一个数组,如下所示: // [2,2,4,4,6,6] 幸运的是Array的结构为此目的有一个称为joined ,让我们使用它: var multipliedByTwo = arrayOfArrays。 映射 {数组在 返回数组。 映射 {中的整数 返回整数* 2 } } var flattened […]

踢您的手动迭代习惯

当约翰·巴科斯(John Backus)于1977年在他的ACM图灵奖演讲中问“编程是否可以从冯·诺伊曼风格中解放出来?”时,他肯定比40年后仍为for(i = 0; i < n; i++)写作的人寄予更高的希望。 冯·诺依曼(Von Neumann)编程语言使用变量来模仿计算机的存储单元。 控制语句详细说明其跳转和测试指令; 和赋值语句模仿其获取,存储和算术。 赋值语句是编程语言的von Neumann瓶颈,使我们可以一次单词的方式思考,就像计算机的瓶颈一样。 到2017年,主要的编程语言已经吸收了函数式编程中最实用的思想。 我在这里说服您,使用高阶函数来处理数据集合通常可以使您的意图比手动迭代更清晰。 从描述如何执行计算作为循环增加计数器的副作用,或者通过从键集中索引到字典开始,我们早就应该进行下一步了。 这种命令式风格要求代码的人工阅读者在脑海中模拟计算机的执行。 那是一件坏事! 取而代之的是在抽象阶梯上上移一个梯级,并根据功能组成描述您的计算工作:过滤,转换和减少输入流。 在我遇到的大多数情况下,数据存储在数组或列表中的事实是出于方便。 存储在其中的数据没有真正排序。 数组通常只是最易于键入的集合,因为所有语言都内置了语法糖。 最好将集合一般认为是容纳某些元素的容器。 在处理它们时,请开始考虑符合给定条件的元素集-这就是过滤器应用程序。 是否需要为每个元素计算一些派生值? 这种转换称为映射。 需要整个集合中的一些总价值吗? 那就是缩小或折叠。 学习这些概念,并使用它们更清楚地表达您的意图。 当您将计算作为增加索引的副作用时,了解它需要您逐步执行所有命令。 当您通过功能组合来表达自己的意图时,隐藏错误的空间就更少了。

F#中的流上的收缩函数

编程并不总是要按时完成任务,进行冲刺,构建应用程序或修正错误,有时您想蜿蜒而行,享受旅程。 F#是一种出色的语言,可用于退步,分区,让世界的烦恼消散,并获得一些乐趣。 我知道是异端,但是是的,编程可能很有趣。 当Apple将Swift定位为儿童的理想学习语言时,它总是让我感到震惊,表面上是因为它要使用多少乐趣。 实际上,我想不出一种有趣的语言,即以陈述为导向的思想的陈旧过时的融合,甚至在刚出厂时就已经过期了。 无论如何,足够的咆哮和狂欢,让我们做些曲折的🙂 定点玩法 在介绍收缩函数之前,让我们简要介绍一下fix函数。 本文很好地解释了Haskell中的函数,其基本思想是fix使我们能够将函数的主体编写为普通函数,而fix负责递归。 修复(1 🙂 :是Haskell中的惰性cons运算符,因此上述函数将生成一个无限的1序列。 由于fix负责递归机制,因此我们将重点放在编写简单函数体的内容上。 当然,如果我们尝试在f#中编写相同的函数,那么在f#中并不是那么简单。 让rec fix f = f(fix f) 设ht = h :: t 让一个=修复(缺点1) 我们迎来了一个 进程由于StackOverflowException而终止。 当然这并不令人震惊,我们都知道f#在减少函数应用程序本身之前使用急切的求值方法将函数参数评估为标准形式。 Haskell做相反的事情,它将函数评估为弱头范式,而未对参数进行评估。 之前的大量技术鬼话可能并没有多大意义,因此请阅读这篇文章,以获得关于评估策略的更容易理解的文章。 底线是我们需要在我们的f#定义中引入thunk(函数的占位符)。 你觉得幸运吗,Thunk? 我们可以像这样滚动自己的重击,或者可以利用如下的内置Lazy类型 惰性定点和无限流| F#代码段 使用惰性定点组合器定义的无限流的示例。 www.fssnip.net 修订的懒惰定义使用懒惰的构造函数将我们每个函数应用程序包装在一起。 let fix : (Lazy -> ‘T) -> Lazy = fun f -> let rec x = […]