1概要

Arrowはhttp://kategory.io/[KΛTEGORY]とhttps://github.com/MarioAriasC/funKTionale[funKTionale]からマージされたライブラリです。

このチュートリアルでは、Arrowの基本と、それがどのようにしてKotlinの関数型プログラミングの力を利用するのに役立つのかを見ていきます。

コアパッケージのデータ型について説明し、エラー処理に関するユースケースを調査します。

2. Mavenの依存関係

プロジェクトにArrowを含めるには、https://search.maven.org/search?q=g:io.arrow-kt%20AND%20a:arrow-coreを追加する必要があります。

<dependency>
    <groupId>io.arrow-kt</groupId>
    <artifactId>arrow-core</artifactId>
    <version>0.7.3</version>
</dependency>


3機能データ型

コアモジュールのデータ型を調べることから始めましょう。


3.1. モナド入門

ここで説明するデータ型のいくつかはモナドです。非常に基本的に、モナドには以下の性質があります。

  • それらは基本的に1つの周りのラッパーである特別なデータ型です

より生の値
** 3つの公開メソッドがあります。

  • ** 値をラップするファクトリメソッド

  • **

    map

  • **

    flatMap

  • これらのメソッドは

    nicely

    、つまり副作用がありません。

Javaの世界では、配列とストリームはモナドですが、https://www.sitepoint.com/how-optional-breaks-the-monad-laws-and-why-it-matters/[オプションではありません]。モナドの詳細についてはhttps://medium.com/beingprofessional/understanding-functor-and-monad-with-a-bag-of-peanuts-8fa702b3f69e[ピーナッツの袋]が役立つことがあります]。

それでは、

arrow-core

モジュールの最初のデータ型を見てみましょう。


3.2.

Id



Id

はArrowの最も単純なラッパーです。

コンストラクタまたはファクトリメソッドを使って作成できます。

val id = Id("foo")
val justId = Id.just("foo");

そして、ラップされた値を取得するための

extract

メソッドがあります。

Assert.assertEquals("foo", id.extract())
Assert.assertEquals(justId, id)


Id

クラスはモナドパターンの要件を満たします。


3.3.

オプション



Option

は、JavaのOptionalと同様に、存在しない可能性がある値をモデル化するためのデータ型です。

技術的にはモナドではありませんが、それでも非常に役に立ちます。

2つのタイプを含めることができます。値を囲む


_Some


ラッパー、または値がない場合は

None_


Option

を作成する方法はいくつかあります。

val factory = Option.just(42)
val constructor = Option(42)
val emptyOptional = Option.empty<Integer>()
val fromNullable = Option.fromNullable(null)

Assert.assertEquals(42, factory.getOrElse { -1 })
Assert.assertEquals(factory, constructor)
Assert.assertEquals(emptyOptional, fromNullable)

  • ここで、注意が必要な点がいくつかあります。** ファクトリメソッドとコンストラクタは、

    null

    に対して異なる動作をするためです。

val constructor : Option<String?> = Option(null)
val fromNullable : Option<String?> = Option.fromNullable(null)
Assert.assertNotEquals(constructor, fromNullable)


  • __KotlinNullPointerException

    __riskを持っていないので、私たちは2番目を好みます:**

try {
    constructor.map { s -> s!!.length }
} catch (e : KotlinNullPointerException) {
    fromNullable.map { s -> s!!.length }
}


3.3.

Either


前に見たように、

Option

には値を持たない(

None

)か、または何らかの値(

Some

)を付けることができます。


Either

はこのパスをさらに進み、2つの値のうちの1つを持つことができます。


Either

には、

right

と__leftとして示される2つの値の型に関する2つの一般的なパラメーターがあります。

val rightOnly : Either<String,Int> = Either.right(42)
val leftOnly : Either<String,Int> = Either.left("foo")

このクラスは「偏りがある」ように設計されています。したがって、右側のブランチにはビジネス価値、たとえば何らかの計算結果を含める必要があります。左のブランチはエラーメッセージあるいは例外さえも保持できます。

したがって、値抽出メソッド(

getOrElse

)は右側に向けて設計されています。

Assert.assertTrue(rightOnly.isRight())
Assert.assertTrue(leftOnly.isLeft())
Assert.assertEquals(42, rightOnly.getOrElse { -1 })
Assert.assertEquals(-1, leftOnly.getOrElse { -1 })


map

メソッドと

flatMap

メソッドでさえも、右側で機能し左側をスキップするように設計されています。

Assert.assertEquals(0, rightOnly.map { it % 2 }.getOrElse { -1 })
Assert.assertEquals(-1, leftOnly.map { it % 2 }.getOrElse { -1 })
Assert.assertTrue(rightOnly.flatMap { Either.Right(it % 2) }.isRight())
Assert.assertTrue(leftOnly.flatMap { Either.Right(it % 2) }.isLeft())

セクション4では、エラー処理に

Either

を使用する方法について説明します。


3.4.

評価



Eval

は操作の評価を制御するように設計されたモナドです。それはメモ化と熱心で怠惰な評価を組み込みでサポートしています。


now

factoryメソッドを使うと、すでに計算された値から

Eval

インスタンスを作成することができます。

val now = Eval.now(1)


map

および

flatMap

操作は遅延して実行されます。

var counter : Int = 0
val map = now.map { x -> counter++; x+1 }
Assert.assertEquals(0, counter)

val extract = map.value()
Assert.assertEquals(2, extract)
Assert.assertEquals(1, counter)


  • counter

    は、

    __ value

    __methodが呼び出された後にのみ変更されることがわかります。


__later


factoryメソッドは、関数から

Eval

インスタンスを作成します。評価は

value__が呼び出されるまで延期され、結果が記憶されます。

var counter : Int = 0
val later = Eval.later { counter++; counter }
Assert.assertEquals(0, counter)

val firstValue = later.value()
Assert.assertEquals(1, firstValue)
Assert.assertEquals(1, counter)

val secondValue = later.value()
Assert.assertEquals(1, secondValue)
Assert.assertEquals(1, counter)

  • 3番目の工場は

    always

    です。

    value

    が呼び出されるたびに、与えられた関数を再計算する

    Eval

    インスタンスを作成します。

var counter : Int = 0
val later = Eval.always { counter++; counter }
Assert.assertEquals(0, counter)

val firstValue = later.value()
Assert.assertEquals(1, firstValue)
Assert.assertEquals(1, counter)

val secondValue = later.value()
Assert.assertEquals(2, secondValue)
Assert.assertEquals(2, counter)


4機能データ型によるエラー処理パターン

例外を投げることによるエラー処理にはいくつかの欠点があります。

ユーザー入力を数値として解析するなど、失敗することが多く、予想通りに失敗するメソッドの場合、例外をスローするのはコストがかかり不要です。コストの大部分は

fillInStackTrace

メソッドから来ています。実際、現代のフレームワークでは、ビジネスロジックに関する驚くほど少ない情報で、スタックトレースが途方もなく長くなることがあります。

さらに、チェック済み例外を処理すると、クライアントのコードが無用に複雑になることがあります。一方、実行時例外では、呼び出し元は例外の可能性についての情報を持っていません。

次に、偶数入力数の最大約数が平方数であるかどうかを確認するためのソリューションを実装します。ユーザー入力は文字列として到着します。この例と一緒に、Arrowのデータ型がエラー処理にどのように役立つかを調査します。


4.1.

Option


によるエラー処理

まず、入力文字列を整数として解析します。

幸いなことに、Kotlinには便利で例外のない方法があります。

fun parseInput(s : String) : Option<Int> = Option.fromNullable(s.toIntOrNull())

解析結果を

Option

にラップします。次に、この初期値をいくつかのカスタムロジックで変換します。

fun isEven(x : Int) : Boolean//...
fun biggestDivisor(x: Int) : Int//...
fun isSquareNumber(x : Int) : Boolean//...


Option

の設計のおかげで、私たちのビジネスロジックは例外処理やif-else分岐で雑然とすることはありません。

fun computeWithOption(input : String) : Option<Boolean> {
    return parseInput(input)
      .filter(::isEven)
      .map(::biggestDivisor)
      .map(::isSquareNumber)
}

ご覧のとおり、これは純粋なビジネスコードであり、技術的な詳細は必要ありません。

クライアントがどのように結果を処理できるかを見てみましょう。

fun computeWithOptionClient(input : String) : String {
    val computeOption = computeWithOption(input)
    return when(computeOption) {
        is None -> "Not an even number!"
        is Some -> "The greatest divisor is square number: ${computeOption.t}"
    }
}

これは素晴らしいことですが、クライアントには入力に問題があったことについての詳細な情報がありません。

それでは、

Either

を使用してエラーケースのより詳細な説明を提供する方法を見てみましょう。


4.2

Either


を使ったエラー処理


Either

でエラーケースに関する情報を返すためのいくつかのオプションがあります。左側には、文字列メッセージ、エラーコード、さらには例外を含めることができます。

今のところ、この目的のためにシールされたクラスを作成します。

sealed class ComputeProblem {
    object OddNumber : ComputeProblem()
    object NotANumber : ComputeProblem()
}

返された

Either

にこのクラスを含めます。 parseメソッドでは

cond

ファクトリ関数を使います。

Either.cond(/Condition/,/Right-side provider/,/Left-side provider/)

そのため、

__Optionではなく、


Ether




parseInput__メソッドで使用します。

fun parseInput(s : String) : Either<ComputeProblem, Int> =
  Either.cond(s.toIntOrNull() != null, { -> s.toInt() }, { -> ComputeProblem.NotANumber } )

これは

Either




__数字かエラーオブジェクトのどちらかで埋められることを意味します。

他のすべての機能は以前と同じになります。ただし、

filter

メソッドは

Either

とは異なります。それは述語だけでなく、述語の偽分岐のための左側のプロバイダを必要とします。

fun computeWithEither(input : String) : Either<ComputeProblem, Boolean> {
    return parseInput(input)
      .filterOrElse(::isEven) { -> ComputeProblem.OddNumber }
      .map (::biggestDivisor)
      .map (::isSquareNumber)
}

これは、フィルタが

false

を返す場合、

Either

の反対側を指定する必要があるためです。

これで、クライアントは自分の入力の何が問題だったのかを正確に把握できます。

fun computeWithEitherClient(input : String) {
    val computeWithEither = computeWithEither(input)
    when(computeWithEither) {
        is Either.Right -> "The greatest divisor is square number: ${computeWithEither.b}"
        is Either.Left -> when(computeWithEither.a) {
            is ComputeProblem.NotANumber -> "Wrong input! Not a number!"
            is ComputeProblem.OddNumber -> "It is an odd number!"
        }
    }
}


5結論

ArrowライブラリはKotlinの機能をサポートするために作成されました。

arrow-coreパッケージで提供されているデータ型を調べました。

それから

Optional



Either

を関数型のエラー処理に使いました。

いつものように、コードはhttps://github.com/eugenp/tutorials/tree/master/kotlin-libraries[over GitHub]から入手可能です。