Open19

代数的データ型 & kotlin-result

shioyangshioyang

TypeScript のような静的型付けを持つ言語では、開発者が定義するカスタム型の多くが代数的データ型(ADT)の概念を利用しています。TypeScript は和型(Sum Types)や積型(Product Types)の形式で代数的データ型をサポートしており、これにより開発者はより柔軟かつ安全にプログラムを設計することができます。

shioyangshioyang

和型(Union Types)
TypeScript では、和型は複数の型のいずれかを取ることができる型を定義するのに使われます。これは | 演算子を使用して表されます。

積型(Intersection Types)
積型は複数の型を組み合わせて新しい型を作ることができます。これは & 演算子を使用して表されます。

shioyangshioyang

代数的データ型って怖くなくて、単にいつも TypeScript で定義している型をイメージしたらいい。
Union と Intersection を使った型定義。

shioyangshioyang

判別にテンプレートリテラルが使えるようになっていたり (ts 4.5)

type OkResult<T> = {
  type: `ok_${string}`;
  payload: T;
};
type ErrorResult = {
  type: `error_${string}`;
  payload: Error;
};
shioyangshioyang

判別した後に、他の型が絞りこまれるようになっていたり (ts 4.6)

function unwrapResult<T>(result: Result<T>): T {
  // payload はここでは T | Error 型
  const { type, payload } = result;
  if (type === "ok") {
    // payload はこの中では T 型
    return payload;
  } else {
    throw payload;
  }
}
shioyangshioyang

なぜResult型を使うのか
・関数シグネチャで、呼び出し元が処理すべきエラーを伝えることができる
・呼び出し元にエラー処理を強制できる
・復帰可能なエラーと復帰不可能なエラーを明確に区別できる
・エラー処理を簡潔に書ける

shioyangshioyang

なぜ Kotlin では検査例外の仕組みを持っていないのか

Kotlinが検査例外(checked exceptions)の仕組みを持っていない理由は、Kotlinの設計哲学と開発者体験に基づいています。検査例外はJavaで広く知られている機能で、メソッドが投げる可能性のある例外を明示的に宣言し、それを呼び出す側でキャッチするか、さらに上位にスローすることを強制します。しかし、Kotlinの開発チームは以下の理由から検査例外を採用していません。

Kotlinの設計は、開発者がより簡潔で読みやすく、メンテナンスしやすいコードを書くことを促進することに重点を置いています。検査例外を含めないことで、この目標に寄与しています。

shioyangshioyang

開発者体験の向上: 検査例外はコードの可読性を低下させ、書くべきコードの量を増やします。Kotlinでは、よりシンプルで読みやすいコードを書くことができるようにすることを目指しています。

柔軟性の確保: 検査例外は、例外をキャッチして処理するか、宣言してさらに上のレイヤーに伝播させるかを開発者に強制します。これにより、ライブラリの設計者がAPIの使用方法を事前に完全に理解していなければならず、将来的にAPIを変更する際の柔軟性が低下します。

実用性の向上: KotlinはJavaのコードと相互運用するように設計されています。Javaの検査例外をそのままKotlinに適用した場合、相互運用時に不都合が生じる可能性があります。Kotlinでは例外を検査しないことで、Javaコードとのスムーズな統合を実現しています。

実践的なエラー処理: 検査例外が意図するところは、エラー処理を強制することによってより堅牢なアプリケーションを作成することですが、実際には開発者が例外処理を適切に行うことを保証するものではありません。Kotlinでは、開発者がより実践的かつ柔軟にエラー処理を行えるようにすることを目指しています。

shioyangshioyang

get() / getError()
戻り値: T? / E?

不確定

unwrap() / unwrapError()
戻り値: T / E

確定

shioyangshioyang

onSuccess() / onFailure()
引数: (T) -> Unit / (E) -> Unit
戻り値: Result<T, E> / Result<T, E>

返す型は変換しない

map() / mapError()
引数: (T) -> U / (E) -> F
戻り値: Result<U, E> / Result<T, F>

返す型を変換する

shioyangshioyang

andThen() / orElse()
引数: (T) -> Result<U, E> / (E) -> Result<T, F>
戻り値: Result<U, E> / Result<T, F>

ラムダ式の返り値が Result
返す型を変換する
処理を行い、その処理の結果を返すことを意図している

flatMap()
引数: (T) -> Result<U, E>
戻り値: Result<U, E>

関数としては andThen() と同じ
セマンティックの違い