25

我正在查看此处找到的 Arrow 库。为什么要使用Option类型而不是 Kotlin 的内置可空值?

4

3 回答 3

29

我已经使用OptionArrow 提供的数据类型一年多了,一开始我们对自己做了完全相同的问题。答案如下。

选项与可空值

如果仅将option数据类型与nullablesKotlin 中的数据类型进行比较,它们几乎是偶数。相同的语义(有或没有一些值),几乎相同的语法(使用 Option 使用map,使用可空值使用安全调用运算符)。

但是在使用时,Options您可以从箭头生态系统中受益!

Arrow生态系统(功能生态系统)

使用时,Options您正在使用Monad Pattern. 将 monad 模式与arrowscala cat 、scalaz等库一起使用时,您可以从几个函数概念中受益。只有 3 个好处的例子(还有很多):

1. 访问其他 Monad

Option不是唯一的!例如,表达避免抛出异常Either非常有用。,并且是其他常见 monad 的示例,它们可以帮助我们(以更好的方式)我们在典型项目中所做的事情。TryValidatedIO

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

结论

我强烈建议您在看到概念符合您需要的语义后立即开始使用 , 等单子OptionEither即使您不确定是否会从功能生态系统中获得其他巨大好处,或者如果您不知道他们很好呢。很快你就会在没有注意到学习曲线的情况下使用它。在我的公司中,我们几乎在所有 Kotlin 项目中都使用它,甚至在面向对象的项目中(占大多数)。

于 2019-05-14T18:22:29.350 回答
14

免责声明:如果您真的想详细讨论为什么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 主要功能的更多信息”

于 2018-02-20T23:06:50.510 回答
5

其他答案没有提到的一件事:你可以拥有Option<Option<SomeType>>你不能拥有的地方SomeType??。或者Option<SomeType?>,就此而言。这对于组合性非常有用。例如考虑Kotlin 的Map.get

abstract operator fun get(key: K): V?

返回与给定键对应的值,或者null如果映射中不存在这样的键。

但是如果V是可空类型呢?然后get返回null时可能是因为映射为给定键存储了一个空值或者因为没有值;你说不出来!如果它返回Option<V>,就不会有问题。

于 2019-06-25T09:06:17.253 回答