Dartでのエラー処理について考えているのだが、SwiftやRustのResultがほしいよ〜となっていた。ついでにKotlinのResultを眺めていると Result<T> となっており、Throwable固定になっていたのがなぜか気になったのでドキュメントを眺めたので自分用メモ。
答え
Parameterizing this class by the type of exception like
Result<T, E>is possible, but raises the following problems:
It increases verboseness without providing improvement for any of the outlined ++Use cases++.
Kotlin currently lacks facility to specify default values for generic type parameters.
It leads to abuse in cases where a user-provided API-specific sealed class would work better.
It is possible to define a separate class like
ResultEx<T, E>that is parametrized by both successful typeTand failed typeE(that must extendThrowable) and then defineResult<T>and atypealiastoResultEx<T, Throwable>. However, this creates its own problems:
Typealiases are quite verbosely rendered by IDE in signatures and there is no clear way on making them better.
We cannot succinctly define
runCatchingfunction and otherCatchingfunctions to make them usable both with and without explicit caught type specification. We'll have to have two different names for such a function: one for a function with an additionalE: Throwabletype parameter that must be specified and another one without it. Moreover, specifyingEon call site requires specifying return type, too, since partial type parameter specification is not currently possible in Kotlin.All in all, it does not seem that the costs outweigh whatever benefits it might bring.
Defining an even more general
Either<L, R>type as a discriminated union between two arbitrary typesLandRand then usingtypealias Result<T> = Either<Throwable, T>raises similar problems with an additional burden of designing functions forEitherthat would not needlessly pollute the namespace of functions applicable toResult. We don't have sufficient motivating use-cases for havingEitherin the Kotlin Standard Library beyond theoretical desire to baseResultupon it.
その他、へえとなったところ
In general, if some API requires its callers to handle failures locally (immediately around or next to the invocation), then it should use nullable types, when these failures do not carry additional business meaning, or domain-specific data types to represent its successful results and failures with any additional business-related data that is needed to process these failures.
Resultクラスは、ドメイン固有のエラーを表すようには設計されていない。nullを返すか必要なら自前でデータ型を定義するべしと書いてる。
sealed class FindUserResult { data class Found(val user: User) : FindUserResult() data class NotFound(val name: String) : FindUserResult() data class MalformedName(val name: String) : FindUserResult() // other cases that need different business-specific handling code } fun findUserByName(name: String): FindUserResult
Exceptions in Kotlin are designed for the failures that usually do not require local handling at each call site. This includes several broad areas — logic and programming errors like index bounds problems and various checks for internal invariants and preconditions, environment problems, out of memory conditions, etc. These failures are usually non-recoverable (or are not supposed to be recovered from) and are handled in some centralized way by logging or otherwise reporting them for troubleshooting, typically terminating application or, sometimes, attempting to restart or to reinitialize an application as a whole or just its failing subsystem.\ This is where default exceptions behaviour to abort current operation and propagate it up the call stack comes in handy.
スローするということは回復不能なのでエラー処理を必要としていないという思想っぽい。とはいえ、IOExceptionとか処理して回復なりしたい例外はあるよねという話も書かれていた。
Kotlin encourages writing code in a functional style. It works well as long as business-specific failures are represented with nullable types or sealed class hierarchies, while other kinds of failures (that are represented by exceptions) do not require any special local handling. However, when interfacing with Java-style APIs that rely heavily on exceptions or otherwise having a need to somehow process exceptions locally (as opposed to propagating them up the call stack), we see a clear lack of primitives in the Kotlin standard library.
try/catchがあるが、それだと扱いにくいこともあるよねって話。
自分が感じている以下のような課題とKotlinのResultが扱っている課題が違いそうだった。
- エラー処理がされるべきときに漏れてしまう
- エラーの網羅性検査ができない