「Unlocking the Power of Arrow 2 0 A Comprehensive Guide」　を見る

KotlinConf 24 の以下
https://www.youtube.com/watch?v=JcFEI8_af3g&t=1s

Arrow -> https://arrow-kt.io/

---

## TypedErrorHandling

### Exception vs Typed Errors

例外は致命的で予期せぬ状況に対処し、型付きエラーはビジネスロジック内でのエラーを安全に管理するために使用されるべきだということ 👇

```kotlin
context(Raise<UserExists>)
suspend fun insertUser(username: String): User = 
    try {
        queries.insert(username)
    } catch (e: SQLException) {
        if (e.isUniqueViolation()) raise(UserExists(username))
        else throw e
    }

```

### How to model errors

- CustomModels ( sealed hierarchies）
    - use case
    - Layering
- Context Parameters

UseCase

```kotlin
sealed interface UserError
data class UserExists(val username: String): UserError
data object UsernameMissing : UserError

context(Raise<UsernameMissing>)
fun HttpRequest.username(): String
```

Service Layer

```kotlin
sealed interface UserRegistrationError
sealed interface UserError: UserRegistrationError
sealed interface PaymentError : UserRegistrationError

suspend fun Raise<UserRegistrationError>.route(
    request: HttpRequest
): HttpResponse {
    val name = request.username()
    val user = insertUser(name)
    user.receivePayment()
    return HttpResponse.CREATED
}
```

Context Parameter 

```kotlin
context(Raise<UserExists>, Raise<PaymentError>)
suspend fun route(request: HttpRequest): HttpResponse {
    val name = request.username()
    val user = insertUser(name)
    user.receivePayment()
    return HttpResponse.CREATED
}
```

エラーを独立して 複数 明示できる

### Why raise

- 組み合わせが容易

```kotlin
context(Raise<String>)
fun everything() {
    val x = 1.right().bind()
    val y = ensureNotNull(2) { "Value was null" }
    ensure(y >= 0) { "y should be >= 0" }
    val z = quiverOutcome().value()
    TODO()
}
```

Raiseが起こった時点（行）で処理が呼び出し元にもどる。　各行に Throwがあるようなイメージだとわかりやすい？

- Raise = CancellationException

```kotlin
context(Raise<UserExists>)
fun transaction(): User =
    transaction {
        queries.insertUser("nomisrev")
    }
```

Coroutineで呼び出し元にCancellationException が流れるように、Raiseも 呼び出し元のながれていくので、キャンセルされたらどうするか を　呼び出し元で判断してもらうという仕組みは一緒

## Resilience

### Schedule

Retry 戦略 の書き方

```kotlin
var count = 0
var user: User? = null
while (isActive) {
    try {
        result = insertUser(name)
        break
    } catch (e: SQLException) {
        if (count >= MAX_RETRIES) throw e
        else delay(BASE_DELAY * 2.0.pow(count++))
    }
}
return user!!
```

👇

```kotlin
Schedule.exponential<SQLException>(BASE_DELAY)
    .and(Schedule.recurs(MAX_RETRIES))
    .jittered() // リトライ間隔のランダムな変動
    .doWhile { e: SQLException, _ -> e.isRetryable() }
    .retry { insertUser(name) }
```

Schedule で よく使いそうなのは たとえば 「5回 、成功するまで繰り返す」 

```kotlin
Schedule.recurs<HttpResponse>(5)
    .doUntil { response, _ -> response.isCorrect() }
    .repeat { client.get("my-url") }
```

### SageScope

同一関数ないで 二つの transactionが動いている という状態

```kotlin
context(Raise<UserRegistrationError>)
suspend fun route(request: HttpRequest): HttpResponse {
    val name = request.username()
    val user = insertUser(name)
    user.receivePayment()
    return HttpResponse.CREATED
}
```

ロールバックが難しいから大抵はよくない

Saga Transaction

マイクロサービスアーキテクチャ であるようなパターン を関数レベルで って意味で Saga? 

```kotlin
context(Raise<UserrExists>, SagaScope)
suspend fun insertUserOrRollback(username: String): User
	= saga({
		insertUser(userName)
	}) { user -> deleteUser(user) }
```

```kotlin
context(Raise<UserRegistrationError>)
suspend fun route(request: HttpRequest): HttpResponse {
    val name = request.username()
    saga {
	    val user = insertUserOrRollback(name)
	    user.receivePayment()
    }.transact()
    return HttpResponse.CREATED
}
```

この辺りは Transaction による　分散 処理を ちゃんと制御するときに 役立ちそう

## Optics

データ構造を便利にcopyする 

```kotlin
val item = OrderItem(Product(id = 1, price = 1.5), quantity = 1)

item.copy(
    product = item.product.copy(
        price = item.product.price * 0.9
    )
)
👇
OrderItem.product.price.modify(item) { it * 1.1 }
```

```kotlin
data class Inventory(val items: List<Product>)

inventory.copy(
    items = inventory.items.map { product ->
        product.copy(price = product.price * 1.1)
    }
)
👇
Inventory.items.every.price.modify(inventory) { it * 1.1 }
```

```kotlin
sealed interface ErrorOrProduct
data class Success(val product: Product) : ErrorOrProduct
data class Failed(val error: Throwable) : ErrorOrProduct

when (errorOrProduct) {
    is Failed -> errorOrProduct
    is Success -> Success(
        errorOrProduct.product.copy(price = errorOrProduct.product.price * 1.1)
    )
}
👇
ErrorOrProduct.success.product.price.modify(errorOrProduct) { it * 1.1 }
```

```kotlin
val item = OrderItem(Product(id = 1, price = 1.5), quantity = 1)

item.copy {
    OrderItem.product.price.transform { it * 1.1 }
    OrderItem.quantity.set(5)
}
```