读书笔记《functional-kotlin》Arrow快速入门
箭头 (http: //arrow-kt.io/) 是一个 Kotlin library 提供 functional 构造、数据类型和其他抽象。 Kotlin 语法强大而灵活,Arrow 利用它来提供非标准的功能。
Arrow 是将两个最成功和最流行的函数库 funKTionale
和 Kategory
合二为一的结果。 2017 年底,两个开发者团体担心分裂会损害整个 Kotlin 社区,决定联手创建一个单一的、统一的功能库。
在本章中,我们将介绍如何使用现有函数来构建新的和更丰富的函数。我们将讨论的一些主题如下:
- Function composition
- Partial application
- Currying
- Memoization
- Pipes
- Optics
functional 编程的一个重要部分是concept 是以与我们使用任何其他类型相同的方式使用函数——作为值、参数、返回等。我们可以对其他类型做的一件事是将它们作为构建块来构建其他类型;相同的概念可以应用于函数。
函数组合是一种使用 existingfunctions 的技术"indexterm"> 函数;类似于 Unix 管道或通道管道,函数的结果值用作下一个函数的参数。
在 Arrow 中,函数组合是一组 infix
扩展函数:
功能 |
说明 |
|
将调用右侧函数的结果作为左侧函数的参数。 |
|
将调用左侧函数的结果作为右侧函数的参数。 |
|
是 |
让我们编写一些函数:
构建 divStrong: (String) -> String
函数,我们组成 div:(String) -> String
和 strong:(String) ->字符串
。换句话说,divStrong
等价于以下代码片段:
对于 spanP:(String) -> String
,我们组成 span:(String) -> (String)
和 p:(String) ->字符串
如下:
请注意,我们使用相同的类型 (String) ->字符串
,但任何函数都可以组合,只要它具有其他函数所需的正确返回类型。
让我们用函数组合重写我们的 Channel
管道示例:
salesSystem: (Quote) -> Unit
函数的行为非常复杂,但它是使用其他函数作为构建块构建的。
通过函数组合,我们用两个 functions 创建第三个函数;使用 partial 应用程序,我们通过将参数传递给现有函数来创建新函数。
Arrow 带有两种部分应用——显式和隐式。
显式风格使用了一系列扩展函数,称为 partially1
、partially2
,一直到部分22
。隐式样式采用一系列扩展,重载 invoke
运算符:
两种样式都可以链接如下:
我们原来的 splitter
函数不是很灵活,因为它直接调用仓库和会计函数。 partialSplitter
函数以 warehouse
和 accounting
为参数解决了这个问题;但是 (Pair
函数不能用于合成。然后,我们部分应用了两个函数——一个 lambda 和一个引用。
一个 管道
函数 接受一个 T
值 和invokes a (T) -> R
函数 with 它:
pipe 类似于 function 组合,但不是生成 新函数,我们可以链接函数调用以生成新值,从而减少嵌套调用。管道是已知在other< /a> 语言,例如 Elm 和 Ocaml,如运算符 |>
:
这两行是等价的,但第一行必须向后理解,第二行应该从左到右阅读:
当 pipe
应用于多参数函数时,使用其变体 pipe2
到 pipe22< /code>,它的行为类似于
partially1
。
将柯里化应用于 n 的 function参数,例如, (A, B) -> R
,将其转换为 n
函数调用链,(A) -> (B) -> R
:
使用 uncurried()
可以将柯里化形式上的函数转换为普通的多参数形式。
currying 和部分应用之间存在一些混淆。一些 作者 将它们视为同义词,但它们是不同的:
差异很大,它们可以帮助我们决定何时使用其中一个:
柯里化 |
部分申请 |
|
返回值 |
当一个 arity N 的函数被柯里化时,它会返回一个 N 大小,(咖喱形式)。 |
当一个函数或 arity N 被部分应用时,它返回一个 arity N - 1< /em>。 |
参数应用 |
柯里化后,只能应用链的第一个参数。 |
任何参数都可以按任何顺序应用。 |
正在恢复 |
可以在柯里化形式上获取一个函数并将其还原为多参数函数。 |
由于部分应用不改变函数形式,因此不适用还原。 |
部分应用程序可以更灵活,但一些函数式风格倾向于柯里化。要掌握的重要一点是,两种样式都不同,并且都受 Arrow 支持。
部分函数(不要与部分应用函数混淆)是一个没有为其参数类型的每个可能值定义的函数。相比之下,total function 是 function 为每个可能的值定义。
upper
函数是偏函数;尽管 null
是有效的 String?
值,但它无法处理空值。如果您尝试运行此代码,它将抛出 NullPointerException
(NPE)。
Arrow 为 (T) -> 类型的部分函数提供显式类型
PartialFunction
R
:
PartialFunction<T, R>
接收谓词 (T) -> Boolean
作为必须返回的第一个参数 true
如果函数是为该特定值定义的。 PartialFunction<T, R>
函数扩展自 (T) -> R
,因此它可以用作普通函数。
在此示例中,代码仍会引发异常,但现在类型为 IllegalArgumentException
(IAE) , 带有信息丰富的消息。
为避免出现异常,我们必须将我们的部分函数转换 为一个总函数:
一种选择是使用返回默认值的 invokeOrElse
函数,以防 s
值未为此函数定义:
第二个选项是使用函数 orElse
使用几个部分函数创建一个总函数:
通过 isDefinedAt(T)
函数我们可以重用内部谓词,在这种情况下,为fizzBuzz
。在 orElse
链中使用时,声明顺序优先,为值定义的第一个部分函数将被执行,而链下的其他函数将被忽略.
身份和 constant 是直截了当< /a> 函数。 identity
函数返回作为参数提供的相同值;类似于加法和乘法的恒等性质,任何数加 0 仍然是同一个数。
constant<T, R>(t: T)
函数返回一个始终返回 t
值的新函数:
我们可以使用 constant
重写我们的 fizzBuzz
值:
恒等函数在函数式编程或数学算法的实现中很有用,例如,SKI 组合微积分中的常数是 K。
Optics 是更新 immutable 数据的抽象结构优雅。一种光学形式是 Lens
(或镜头,取决于库的实现)。 A Lens
是一个功能引用,它可以聚焦(因此得名)一个结构并读取、写入或修改其目标:
要从现有值创建新的 Laptop
值,我们需要使用几种嵌套的复制方法和引用。在这个例子中,它并没有那么糟糕,但是你可以想象在一个更复杂的数据结构中,事情可能会变得疯狂。
让我们编写我们的第一个 Lens
values:
laptopPrice
值是我们使用函数 Lens<Laptop, Double>
>Lens<S,T,A,B>(实际上是Lens.invoke
)。 Lens
两个函数作为参数,如 get: (S) -> A
和 设置:(B) -> (S) -> T
。
如您所见, set
是一个柯里化函数,因此您可以这样编写集合:
根据您的喜好,哪个更容易阅读和编写。
现在您已经有了第一个镜头,可以使用它来设置、读取和修改笔记本电脑的价格。不是太令人印象深刻,但镜头的魔力在于将它们结合起来:
我们创建了 laptopMemorySize
,结合了从 Laptop
到 memorySize
的镜头;然后,我们可以设置笔记本电脑的价格和修改它的内存。
尽管镜头有多酷,但它看起来像很多样板代码。不要害怕,Arrow 可以为您生成这些镜头。