@@ -35,19 +35,32 @@ dependencies {
35
35
36
36
## Introduction
37
37
38
- The [ ` Result ` ] [ result ] monad has two subtypes, [ ` Ok<V> ` ] [ result-ok ]
39
- representing success and containing a ` value ` , and [ ` Err<E> ` ] [ result-err ] ,
40
- representing failure and containing an ` error ` .
38
+ In functional programming, the result [ ` Result ` ] [ result ] type is a monadic type
39
+ holding a returned [ value] [ result-value ] or an [ error] [ result-error ] .
41
40
42
- Mappings are available on the [ wiki] [ wiki ] to assist those with experience
43
- using the ` Result ` type in other languages:
41
+ To indicate an operation that succeeded, return an [ ` Ok(value) ` ] [ result-Ok ]
42
+ with the successful ` value ` . If it failed, return an [ ` Err(error) ` ] [ result-Err ]
43
+ with the ` error ` that caused the failure.
44
44
45
- - [ Elm] ( https://github.com/michaelbull/kotlin-result/wiki/Elm )
46
- - [ Haskell] ( https://github.com/michaelbull/kotlin-result/wiki/Haskell )
47
- - [ Rust] ( https://github.com/michaelbull/kotlin-result/wiki/Rust )
48
- - [ Scala] ( https://github.com/michaelbull/kotlin-result/wiki/Scala )
45
+ This helps to define a clear happy/unhappy path of execution that is commonly
46
+ referred to as [ Railway Oriented Programming] [ rop ] , whereby the happy and
47
+ unhappy paths are represented as separate railways.
49
48
50
- ## Read More
49
+ ### Overhead
50
+
51
+ The ` Result ` type is modelled as an
52
+ [ inline value class] [ kotlin-inline-classes ] . This achieves zero object
53
+ allocations on the happy path.
54
+
55
+ A full breakdown, with example output Java code, is available in the
56
+ [ Overhead] [ wiki-Overhead ] design doc.
57
+
58
+ ### Multiplatform Support
59
+
60
+ ` kotlin-result ` targets all three tiers outlined by the
61
+ [ Kotlin/Native target support] [ kotlin-native-target-support ]
62
+
63
+ ### Read More
51
64
52
65
Below is a collection of videos & articles authored on the subject of this
53
66
library. Feel free to open a pull request on [ GitHub] [ github ] if you would like
@@ -60,11 +73,18 @@ to include yours.
60
73
- [[ JP] KotlinでResult型使うならkotlin-resultを使おう] ( https://note.com/yasukotelin/n/n6d9e352c344c )
61
74
- [[ JP] kotlinのコードにReturn Resultを組み込む] ( https://nnao45.hatenadiary.com/entry/2019/11/30/224820 )
62
75
76
+ Mappings are available on the [ wiki] [ wiki ] to assist those with experience
77
+ using the ` Result ` type in other languages:
78
+
79
+ - [ Elm] ( https://github.com/michaelbull/kotlin-result/wiki/Elm )
80
+ - [ Haskell] ( https://github.com/michaelbull/kotlin-result/wiki/Haskell )
81
+ - [ Rust] ( https://github.com/michaelbull/kotlin-result/wiki/Rust )
82
+ - [ Scala] ( https://github.com/michaelbull/kotlin-result/wiki/Scala )
83
+
63
84
## Getting Started
64
85
65
- The idiomatic approach to modelling operations that may fail in Railway
66
- Oriented Programming is to avoid throwing an exception and instead make the
67
- return type of your function a ` Result ` .
86
+ Below is a simple example of how you may use the ` Result ` type to model a
87
+ function that may fail.
68
88
69
89
``` kotlin
70
90
fun checkPrivileges (user : User , command : Command ): Result <Command , CommandError > {
@@ -76,10 +96,9 @@ fun checkPrivileges(user: User, command: Command): Result<Command, CommandError>
76
96
}
77
97
```
78
98
79
- To incorporate the ` Result ` type into an existing codebase that throws
80
- exceptions, you can wrap functions that may ` throw ` with
81
- [ ` runCatching ` ] [ result-runCatching ] . This will execute the block of code and
82
- ` catch ` any ` Throwable ` , returning a ` Result<T, Throwable> ` .
99
+ When interacting with code outside your control that may throw exceptions, wrap
100
+ the call with [ ` runCatching ` ] [ result-runCatching ] to capture its execution as a
101
+ ` Result<T, Throwable> ` :
83
102
84
103
``` kotlin
85
104
val result: Result <Customer , Throwable > = runCatching {
@@ -100,8 +119,7 @@ val result: Result<Customer, String> = customers
100
119
101
120
Both success and failure results can be transformed within a stage of the
102
121
railway track. The example below demonstrates how to transform an internal
103
- program error (` UnlockError ` ) into an exposed client error
104
- (` IncorrectPassword ` ).
122
+ program error ` UnlockError ` into the exposed client error ` IncorrectPassword ` .
105
123
106
124
``` kotlin
107
125
val result: Result <Treasure , UnlockResponse > =
@@ -130,21 +148,22 @@ tokenize(command.toLowerCase())
130
148
131
149
### Binding (Monad Comprehension)
132
150
133
- The ` binding ` keyword allows multiple calls that each return a ` Result ` to be
134
- chained imperatively. When inside a ` binding ` block, the ` .bind() ` function is
135
- accessible on any ` Result ` . Each call to ` bind ` will attempt to unwrap the
136
- ` Result ` and store its value, returning early if any ` Result ` is an ` Err ` .
151
+ The [ ` binding ` ] [ result-binding ] function allows multiple calls that each return
152
+ a ` Result ` to be chained imperatively. When inside a ` binding ` block, the
153
+ ` bind() ` function is accessible on any ` Result ` . Each call to ` bind ` will
154
+ attempt to unwrap the ` Result ` and store its value, returning early if any
155
+ ` Result ` is an error.
137
156
138
- In the example below, should ` functionX() ` return an ` Err ` , then execution will
139
- skip both ` functionY() ` and ` functionZ() ` , instead storing the ` Err ` from
157
+ In the example below, should ` functionX() ` return an error , then execution will
158
+ skip both ` functionY() ` and ` functionZ() ` , instead storing the error from
140
159
` functionX ` in the variable named ` sum ` .
141
160
142
161
``` kotlin
143
- fun functionX (): Result <Int , DomainError > { .. . }
144
- fun functionY (): Result <Int , DomainError > { .. . }
145
- fun functionZ (): Result <Int , DomainError > { .. . }
162
+ fun functionX (): Result <Int , SumError > { .. . }
163
+ fun functionY (): Result <Int , SumError > { .. . }
164
+ fun functionZ (): Result <Int , SumError > { .. . }
146
165
147
- val sum: Result <Int , DomainError > = binding {
166
+ val sum: Result <Int , SumError > = binding {
148
167
val x = functionX().bind()
149
168
val y = functionY().bind()
150
169
val z = functionZ().bind()
@@ -154,18 +173,18 @@ val sum: Result<Int, DomainError> = binding {
154
173
println (" The sum is $sum " ) // prints "The sum is Ok(100)"
155
174
```
156
175
157
- The ` binding ` keyword primarily draws inspiration from
176
+ The ` binding ` function primarily draws inspiration from
158
177
[ Bow's ` binding ` function] [ bow-bindings ] , however below is a list of other
159
178
resources on the topic of monad comprehensions.
160
179
161
- - [ Monad comprehensions - Arrow (Kotlin)] [ arrow-monad-comprehension ]
162
- - [ Monad comprehensions - Bow (Swift)] [ bow-monad-comprehension ]
163
- - [ For comprehensions - Scala] [ scala-for-comprehension ]
180
+ - [ Monad comprehensions - Arrow (Kotlin)] ( https:// arrow-kt.io/docs/0.10/patterns/monad_comprehensions/ )
181
+ - [ Monad comprehensions - Bow (Swift)] ( https:// bow-swift.io/docs/patterns/ monad-comprehensions )
182
+ - [ For comprehensions - Scala] ( https://docs. scala-lang.org/tour/ for-comprehensions.html )
164
183
165
- #### Coroutine Support
184
+ #### Coroutine Binding Support
166
185
167
- Use of suspending functions within a ` binding ` block requires an additional
168
- dependency:
186
+ Use of suspending functions within a ` coroutineBinding ` block requires an
187
+ additional dependency:
169
188
170
189
``` kotlin
171
190
dependencies {
@@ -174,9 +193,12 @@ dependencies {
174
193
}
175
194
```
176
195
177
- The coroutine implementation of ` binding ` has been designed so that the first
178
- call to ` bind() ` that fails will cancel all child coroutines within the current
179
- coroutine scope.
196
+ The [ ` coroutineBinding ` ] [ result-coroutineBinding ] function runs inside a
197
+ [ ` coroutineScope ` ] [ kotlin-coroutineScope ] , facilitating _ concurrent
198
+ decomposition of work_ .
199
+
200
+ When any call to ` bind() ` inside the block fails, the scope fails, cancelling
201
+ all other children.
180
202
181
203
The example below demonstrates a computationally expensive function that takes
182
204
five milliseconds to compute being eagerly cancelled as soon as a smaller
@@ -187,7 +209,7 @@ suspend fun failsIn5ms(): Result<Int, DomainErrorA> { ... }
187
209
suspend fun failsIn1ms (): Result <Int , DomainErrorB > { .. . }
188
210
189
211
runBlocking {
190
- val result: Result <Int , BindingError > = binding {
212
+ val result: Result <Int , BindingError > = coroutineBinding { // this creates a new CoroutineScope
191
213
val x = async { failsIn5ms().bind() }
192
214
val y = async { failsIn1ms().bind() }
193
215
x.await() + y.await()
@@ -207,20 +229,15 @@ Result monad is present, including:
207
229
- [ Rust] ( https://doc.rust-lang.org/std/result/ )
208
230
- [ Scala] ( http://www.scala-lang.org/api/2.12.4/scala/util/Either.html )
209
231
210
- It also iterates on other Result libraries written in Kotlin, namely:
211
-
212
- - [ danneu/kotlin-result] ( https://github.com/danneu/kotlin-result )
213
- - [ kittinunf/Result] ( https://github.com/kittinunf/Result )
214
- - [ npryce/result4k] ( https://github.com/npryce/result4k )
215
-
216
- Improvements on the existing solutions include:
232
+ Improvements on existing solutions such the stdlib include:
217
233
234
+ - Reduced runtime overhead with zero object allocations on the happy path
218
235
- Feature parity with Result types from other languages including Elm, Haskell,
219
236
& Rust
220
237
- Lax constraints on ` value ` /` error ` nullability
221
238
- Lax constraints on the ` error ` type's inheritance (does not inherit from
222
239
` Exception ` )
223
- - Top level ` Ok ` and ` Err ` classes avoids qualifying usages with
240
+ - Top level ` Ok ` and ` Err ` functions avoids qualifying usages with
224
241
` Result.Ok ` /` Result.Err ` respectively
225
242
- Higher-order functions marked with the ` inline ` keyword for reduced runtime
226
243
overhead
@@ -239,52 +256,10 @@ in a real world scenario.
239
256
It hosts a [ ktor] [ ktor ] server on port 9000 with a ` /customers ` endpoint. The
240
257
endpoint responds to both ` GET ` and ` POST ` requests with a provided ` id ` , e.g.
241
258
` /customers/100 ` . Upserting a customer id of 42 is hardcoded to throw an
242
- [ ` SQLException ` ] [ customer-42 ] to demonstrate how the ` Result ` type can [ map
243
- internal program errors] [ update-customer-error ] to more appropriate
259
+ [ ` SQLException ` ] [ customer-42 ] to demonstrate how the ` Result ` type can
260
+ [ map internal program errors] [ update-customer-error ] to more appropriate
244
261
user-facing errors.
245
262
246
- ### Payloads
247
-
248
- #### Fetch customer information
249
-
250
- ```
251
- $ curl -i -X GET 'http://localhost:9000/customers/1'
252
- ```
253
-
254
- ```
255
- HTTP/1.1 200 OK
256
- Content-Type: application/json; charset=UTF-8
257
- Content-Length: 84
258
-
259
- {
260
- "firstName": "Michael",
261
- "lastName": "Bull",
262
-
263
- }
264
- ```
265
-
266
- #### Add new customer
267
-
268
- ```
269
- $ curl -i -X POST \
270
- -H "Content-Type:application/json" \
271
- -d \
272
- '{
273
- "firstName": "Your",
274
- "lastName": "Name",
275
-
276
- }' \
277
- 'http://localhost:9000/customers/200'
278
- ```
279
-
280
- ```
281
- HTTP/1.1 201 Created
282
- Content-Type: text/plain; charset=UTF-8
283
- Content-Length: 16
284
-
285
- Customer created
286
- ```
287
-
288
263
## Contributing
289
264
290
265
Bug reports and pull requests are welcome on [ GitHub] [ github ] .
@@ -295,22 +270,27 @@ This project is available under the terms of the ISC license. See the
295
270
[ ` LICENSE ` ] ( LICENSE ) file for the copyright information and licensing terms.
296
271
297
272
[ result ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt#L10
298
- [ result-ok ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt#L35
299
- [ result-err ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt#L58
300
- [ result-runCatching ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Factory.kt#L11
273
+ [ result-value ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt#L55
274
+ [ result-error ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt#L59
275
+ [ result-Ok ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt#L9
276
+ [ result-Err ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt#L17
277
+ [ kotlin-inline-classes ] : https://kotlinlang.org/docs/inline-classes.html
278
+ [ wiki-Overhead ] : https://github.com/michaelbull/kotlin-result/wiki/Overhead
279
+ [ rop ] : https://fsharpforfunandprofit.com/rop/
280
+ [ kotlin-native-target-support ] : https://kotlinlang.org/docs/native-target-support.html
281
+ [ github ] : https://github.com/michaelbull/kotlin-result
301
282
[ wiki ] : https://github.com/michaelbull/kotlin-result/wiki
283
+ [ result-runCatching ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Factory.kt#L11
284
+ [ result-binding ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Binding.kt#L28
285
+ [ bow-bindings ] : https://bow-swift.io/docs/patterns/monad-comprehensions/#bindings
286
+ [ result-coroutineBinding ] : https://github.com/michaelbull/kotlin-result/blob/master/kotlin-result-coroutines/src/commonMain/kotlin/com/github/michaelbull/result/coroutines/CoroutineBinding.kt#L42
287
+ [ kotlin-coroutineScope ] : https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
302
288
[ unit-tests ] : https://github.com/michaelbull/kotlin-result/tree/master/kotlin-result/src/commonTest/kotlin/com/github/michaelbull/result
303
289
[ example ] : https://github.com/michaelbull/kotlin-result/tree/master/example/src/main/kotlin/com/github/michaelbull/result/example
304
290
[ swalschin-example ] : https://github.com/swlaschin/Railway-Oriented-Programming-Example
305
291
[ ktor ] : http://ktor.io/
306
292
[ customer-42 ] : https://github.com/michaelbull/kotlin-result/blob/master/example/src/main/kotlin/com/github/michaelbull/result/example/repository/InMemoryCustomerRepository.kt#L38
307
293
[ update-customer-error ] : https://github.com/michaelbull/kotlin-result/blob/master/example/src/main/kotlin/com/github/michaelbull/result/example/service/CustomerService.kt#L50
308
- [ github ] : https://github.com/michaelbull/kotlin-result
309
- [ bow-bindings ] : https://bow-swift.io/docs/patterns/monad-comprehensions/#bindings
310
- [ bow-monad-comprehension ] : https://bow-swift.io/docs/patterns/monad-comprehensions
311
- [ scala-for-comprehension ] : https://docs.scala-lang.org/tour/for-comprehensions.html
312
- [ arrow-monad-comprehension ] : https://arrow-kt.io/docs/0.10/patterns/monad_comprehensions/
313
- [ either-syntax ] : https://arrow-kt.io/docs/0.10/apidocs/arrow-core-data/arrow.core/-either/#syntax
314
294
315
295
[ badge-android ] : http://img.shields.io/badge/-android-6EDB8D.svg?style=flat
316
296
[ badge-android-native ] : http://img.shields.io/badge/support-[AndroidNative]-6EDB8D.svg?style=flat
0 commit comments