我正在查看此处找到的 Arrow 库。为什么要使用Option
类型而不是 Kotlin 的内置可空值?
3 回答
我已经使用Option
Arrow 提供的数据类型一年多了,一开始我们对自己做了完全相同的问题。答案如下。
选项与可空值
如果仅将option
数据类型与nullables
Kotlin 中的数据类型进行比较,它们几乎是偶数。相同的语义(有或没有一些值),几乎相同的语法(使用 Option 使用map
,使用可空值使用安全调用运算符)。
但是在使用时,Options
您可以从箭头生态系统中受益!
Arrow生态系统(功能生态系统)
使用时,Options
您正在使用Monad Pattern
. 将 monad 模式与arrow、scala cat 、scalaz等库一起使用时,您可以从几个函数概念中受益。只有 3 个好处的例子(还有很多):
1. 访问其他 Monad
Option
不是唯一的!例如,表达和避免抛出异常Either
非常有用。,并且是其他常见 monad 的示例,它们可以帮助我们(以更好的方式)我们在典型项目中所做的事情。Try
Validated
IO
2. monads+abstractions之间的转换
您可以轻松地将一个 monad 转换为另一个。你有一个Try
但想返回(并表达)一个Either
?只需转换为它。你有一个Either
但不关心错误?只需转换为Option
.
val foo = Try { 2 / 0 }
val bar = foo.toEither()
val baz = bar.toOption()
这种抽象还可以帮助您创建不关心容器(monad)本身的函数,只关心内容。例如,您可以通过以下方式创建Sum(anyContainerWithBigDecimalInside, anotherContainerWithBigDecimal)
与ANY MONAD一起使用的扩展方法(更准确地说:“到任何 applicative 实例”):
fun <F> Applicative<F>.sum(vararg kinds: Kind<F, BigDecimal>): Kind<F, BigDecimal> {
return kinds.reduce { kindA, kindB ->
map(kindA, kindB) { (a, b) -> a.add(b) }
}
}
理解起来有点复杂,但非常有用且易于使用。
3. Monad 推导
从 nullables 到 monads 不仅仅是将安全调用运算符更改为map
调用。看看箭头作为“Monad Comprehensions”模式的实现提供的“绑定”功能:
fun calculateRocketBoost(rocketStatus: RocketStatus): Option<Double> {
return binding {
val (gravity) = rocketStatus.gravity
val (currentSpeed) = rocketStatus.currentSpeed
val (fuel) = rocketStatus.fuel
val (science) = calculateRocketScienceStuff(rocketStatus)
val fuelConsumptionRate = Math.pow(gravity, fuel)
val universeStuff = Math.log(fuelConsumptionRate * science)
universeStuff * currentSpeed
}
}
rocketStatus
上面示例中使用的所有函数以及参数的属性都是Options
. 在binding
块内,flatMap
调用是为我们抽象的。代码更容易阅读(和编写),您无需检查值是否存在,如果其中一些不存在,则计算将停止,结果将是带有None
!的选项
现在试着想象这段代码带有空验证。不仅safe call operators
而且可能还有if null then return
代码路径。不是更难吗?
此外,上面的示例使用Option
了 monad 理解作为一种抽象,但真正的力量是当你将它与IO之类的 monad 一起使用时,你可以以与上面完全相同的“干净、顺序和命令式”的方式抽象异步代码执行:O
结论
我强烈建议您在看到概念符合您需要的语义后立即开始使用 , 等单子Option
,Either
即使您不确定是否会从功能生态系统中获得其他巨大好处,或者如果您不知道他们很好呢。很快你就会在没有注意到学习曲线的情况下使用它。在我的公司中,我们几乎在所有 Kotlin 项目中都使用它,甚至在面向对象的项目中(占大多数)。
免责声明:如果您真的想详细讨论为什么Arrow有用,请前往https://soundcloud.com/user-38099918/arrow-functional-library并听取其中一位工作人员的意见. (5:35 分钟)
创建和使用该库的人希望与创建它的人不同地使用 Kotlin,并使用“类似于 Scala、Haskell 和其他 FP 语言处理可选值的方式的 Option 数据类型”。
这只是定义您不知道其输出的值的返回类型的另一种方式。
让我给你看三个版本:
Kotlin 中的可空性
val someString: String? = if (condition) "String" else null
具有另一个值的对象
val someString: String = if (condition) "String" else ""
箭头版本
val someString: Option<String> = if (condition) Some("String") else None
Kotlin 逻辑的一个主要部分是永远不要使用可空类型,例如String?
,但在与 Java 交互时需要使用它。这样做时,您需要使用诸如非空断言string?.split("a")
之类的安全调用。 string!!.split("a")
我认为在使用 Java 库时使用安全调用是完全有效的,但是Arrow 的人似乎有不同的想法并且想一直使用他们的逻辑。
使用 Arrow 逻辑的好处是“使用户能够定义构建在更高阶抽象之上的纯 FP 应用程序和库。使用下面的列表来了解有关 Λrrow 主要功能的更多信息”。
其他答案没有提到的一件事:你可以拥有Option<Option<SomeType>>
你不能拥有的地方SomeType??
。或者Option<SomeType?>
,就此而言。这对于组合性非常有用。例如考虑Kotlin 的Map.get
:
abstract operator fun get(key: K): V?
返回与给定键对应的值,或者
null
如果映射中不存在这样的键。
但是如果V
是可空类型呢?然后get
返回null
时可能是因为映射为给定键存储了一个空值或者因为没有值;你说不出来!如果它返回Option<V>
,就不会有问题。