web-dev-qa-db-ja.com

Kotlin nullableの代わりにArrow's Optionsを使用する理由

私は見つかったArrowライブラリを見ていた here 。 Kotlinの組み込みnullableの代わりにOption型を使用したいのはなぜですか?

15
pavlos163

免責事項Arrowが便利な理由について詳細な説明が必要な場合は、先に進んでください https://soundcloud.com/user-38099918/arrow-functional-library にアクセスして、作業をしている人の1人に耳を傾けます。 (5:35min)

その単純なライブラリを作成して使用する人々は、ライブラリを作成して使用する人々とは異なる方法でKotlinを使用したいと考えています "Scala、Haskell、および他のFP言語の処理に類似したOptionデータ型オプションの値 "

これは、出力がわからない戻り値の型を定義するもう1つの方法です。

3つのバージョンを紹介します。

Kotlinのnull可能性

_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ロジックの主要な部分は、決して__ nullable types を_String?_のように使用しないことですが、Javaとの相互運用時には使用する必要があります。その場合、string?.split("a")または not-null assertionstring!!.split("a")のような安全な呼び出しを使用する必要があります。

Javaライブラリを使用するときに安全な呼び出しを使用することは完全に有効であると思いますが、 Arrow guys は異なる考え方をしていて、常にロジックを使用したいと考えています。

Arrowロジックを使用する利点は "ユーザーに純粋なFPより高次の抽象化の上に構築されたアプリとライブラリを定義する権限を与えることです。rrowの主な機能の詳細については、以下のリストを使用してください"

Arrowが提供するOptionデータ型を1年以上使用しており、最初はまったく同じ質問を自分自身で行いました。答えは次のとおりです。

オプションとnull可能

Kotlinでoptionデータ型だけをnullablesと比較すると、それらはほぼ偶数です。同じ意味(値があるかどうかにかかわらず)、ほぼ同じ構文(オプションではmapを使用し、null許容値では safe call operator を使用します)。

しかし、Optionsを使用すると、arrowエコシステムのメリットを活用できる可能性があります。

Arrowエコシステム(機能的エコシステム)

Optionsを使用する場合、Monad Patternを使用します。 arrowscala catsscalaz のようなライブラリでモナドパターンを使用する場合、いくつかの機能的な概念から利益を得ることができます。メリットの例は3つだけです(それ以上のものがあります)。

1.他のモナドへのアクセス(オプションだけではありません)

expressおよび例外をスローするのを避ける例外)に役立つか、試してください、検証済み、IOなど。一般的なプロジェクトで行うことを(より良い方法で)行うのに役立つ非常に一般的なモナド。

2.モナドと抽象化の間の変換

あるモナドを別のモナドに簡単に変換できます。あなたはTryを持っていますが、Eitherを返したい(そして表現したい)ですか?それに変換するだけです。あなたはEitherを持っていますが、エラーを気にしませんか? Optionに変換するだけです。

val foo = Try { 2 / 0 }
val bar = foo.toEither()
val baz = bar.toOption()

この抽象化は、コンテナー(モナド)自体ではなく、コンテンツのみに関係しない関数を作成するのにも役立ちます。たとえば、ANY MONADで機能する拡張メソッドSum(bigDecimal,anotherBigDecimal)を作成できます(より正確には: "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.モナド内包表記

Nullableからモナドへの移行は、安全な呼び出し演算子を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!のオプションになります。

代わりに、このコードをnull検証で想像してみてください。 safe call operatorsだけでなく、if null then returnコードパスもおそらく使用できます。もっと大変ではありませんか?

また、上記の例ではOptionを使用していますが、モナド内包表記を抽象化として真の力は、非同期コードを抽象化できる [〜#〜] io [〜#〜] のようなモナドで使用する場合です。上記とまったく同じ「クリーンでシーケンシャルで命令的な」方法で実行:O

結論

機能的なエコシステムから他の大きなメリットを享受するかどうかわからない場合でも、コンセプトが必要なセマンティクスに適合したらすぐに、OptionEitherなどのモナドの使用を開始することを強くお勧めします彼らをまだよく知っている。すぐに、学習曲線に気付かずにそれを使用するようになります。私の会社では、オブジェクト指向プロジェクト(大半)であっても、ほとんどすべてのKotlinプロジェクトで使用しています。

16

他の答えが言及していないことの1つ:Option<Option<SomeType>>できない場所SomeType??。またはOption<SomeType?>、 そのことについては。これは構成性に非常に役立ちます。例えば。検討 KotlinのMap.get

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

指定されたキーに対応する値を返します。そのようなキーがマップに存在しない場合はnullを返します。

しかし、Vがnull許容型である場合はどうなりますか?その場合、getnullを返すのは、マップが指定されたキーのnull値を格納したか、値がなかったためです。わかりません!返された場合Option<V>、問題はありません。

2
Alexey Romanov