Leon K
08/23/2019, 6:04 PMnullable
types, which actually are a big reason why Kotlin is loved so much.
but as soon as more complex Problems come into play, we have to go to rather ugly exception-style code, or use Either
/ Try
types from libraries like arrow-kt ( or create them ourselves)
while using Try
and either
types isn't a big problem by itself, it quickly looses a huge amount of Kotlins consiseness, because it makes you write
kt
val someTry: Try<List<Int>> = someTryReturner()
val result = someTry
.map { it.map { it * 2 } }
.map { it.sum() }
.map { "result is ${it.toString()}" }
.getOrElse { exception -> "There was an error: ${exception.message!!}" }
println(result)
This would be a lot easier when written with the nullable type:
kt
val someNullable: List<Int>? = someNullableReturner()
val result = someNullable
?.map { it * 2 }
?.sum()
?.let{ "result is ${it.toString()}" }
?: "There was an error, but i don't know what kind of error D:"
println(result)
Here were obviously missing out on the error information.
because of this, i propose to support a Result<E, V>
-like type, that behaves like the already existing nullable. my proposed syntax would look like this:
kt
val someExceptional: List<Int>?? = someExceptionalReturner()
val result = someExceptional
??.map { it * 2 }
??.sum()
??.let{ "result is ${it.toString()}" }
?: { exception -> "There was an error: ${exception.message!!}" }
println(result)
this would make code a lot more readable, and could strongly help to encourage developers to actually handle their errors properly.
one additional thing that should be included with this would be a runExceptional
function, that takes a lambda-block that could throw an exception and turns it into an exceptional type.
another possibility would be to actually have a more terse way of turning a throwing function into an exeptional:
fun thisThrows(): String {
throw SomeException()
}
val someExceptional: String?? = thisThrows??()
the questionmarks could directly turn any function that could throw into an exceptional-type. (this syntax is not possible, as it would be ambiguous in situations where you had a (() -> T)??
value that gets executed, but there should be a solution to that problem.Mike
08/23/2019, 6:21 PMmap/flatMap
statements but it's still doing the chaining.Leon K
08/23/2019, 6:42 PM?
operator, but kotlin does and that singlehandedly makes null-handling a piece of cake. if only this would also apply to exception handlingraulraja
08/23/2019, 6:50 PMraulraja
08/23/2019, 6:51 PMraulraja
08/23/2019, 6:51 PMLeon K
08/23/2019, 6:52 PMLeon K
08/23/2019, 6:55 PMkt
val maybeSomething: (SomeException | String) = foo()
when(maybeSomething) {
is SomeException -> // handle error
is String -> // do something
}
Leon K
08/23/2019, 6:55 PMmap
and flatmap
methods could work on unions...Leon K
08/23/2019, 6:57 PMfun <T, O> (Throwable | T).map(transform: (T) -> (Throwable | O) = ...
but even that would be complicated...Leon K
08/23/2019, 7:01 PMval foo: (Int | String) = foo()
foo.let<Int> { it + 2 }
.let<String>{ "$it is a string" }
maybe?raulraja
08/23/2019, 7:06 PMtypealias StringOrInt= String | Int
val foo: StringOrInt = "ok"
val bar: StringOrInt = 1
val x: StringOrInt = foo + bar //ok because operator is polymorphic over semigroup combine
val y: Int = foo //not ok
val z: String = foo //not ok
raulraja
08/23/2019, 7:08 PMraulraja
08/23/2019, 7:08 PMraulraja
08/23/2019, 7:09 PMLeon K
08/23/2019, 7:11 PMraulraja
08/23/2019, 7:13 PMLeon K
08/23/2019, 7:14 PMwhen
statement?raulraja
08/23/2019, 7:14 PMraulraja
08/23/2019, 7:14 PMraulraja
08/23/2019, 7:15 PMraulraja
08/23/2019, 7:15 PMraulraja
08/23/2019, 7:15 PMraulraja
08/23/2019, 7:17 PMLeon K
08/23/2019, 7:18 PMraulraja
08/23/2019, 7:19 PMraulraja
08/23/2019, 7:20 PMLeon K
08/23/2019, 7:20 PMflatMap
operator function / ?.
thingy? that would be great indeedraulraja
08/23/2019, 7:20 PMraulraja
08/23/2019, 7:20 PM!
Leon K
08/23/2019, 7:20 PMraulraja
08/23/2019, 7:20 PMraulraja
08/23/2019, 7:21 PMLeon K
08/23/2019, 7:23 PM?
in the way it would be used, to actually be helpful in the readability of code ( at least in the short term)Leon K
08/23/2019, 7:25 PMraulraja
08/24/2019, 10:35 AMraulraja
08/24/2019, 10:35 AMraulraja
08/24/2019, 10:35 AMraulraja
08/24/2019, 10:35 AMraulraja
08/24/2019, 10:37 AMraulraja
08/24/2019, 10:39 AM!
with flatMap
providing the same effect of flattening the program as suspension does in coroutines but without the need for a runtime, just that a data type is higher kinded and contains a flatMap function with the right shape (A) -> Kind<F, B>
and that is easily true for a lot of data types today in Kotlin including the entire Iterable.flatMap hierarchyraulraja
08/24/2019, 10:39 AMraulraja
08/24/2019, 10:40 AMraulraja
08/24/2019, 10:40 AMraulraja
08/24/2019, 10:40 AMraulraja
08/24/2019, 10:41 AMraulraja
08/24/2019, 10:42 AMraulraja
08/24/2019, 10:43 AMfor in
but it exposes this solely as a statement which is only useful to perform effects and it does not work as expression otherwise for in could be used:raulraja
08/24/2019, 10:45 AMval fa: F<A> =
for {
a in fa
b in fb(b)
b
}
raulraja
08/24/2019, 10:45 AMraulraja
08/24/2019, 10:46 AMF<A>
for Kind<F, A>
so Kotlin users never have to deal with Kind<F, A> ever againraulraja
08/24/2019, 10:48 AMraulraja
08/24/2019, 10:51 AMfor
could be optional and the body changed from an statement to also accept to be an expression when its in an assignment or returnraulraja
08/24/2019, 10:53 AMLeon K
08/24/2019, 11:24 AMraulraja
08/24/2019, 11:29 AMLeon K
08/24/2019, 11:41 AMraulraja
08/24/2019, 11:49 AMflatMap
specially is something that would attract many developers and it does not change the way Kotlin code works today while allowing an abstraction that would appeal to many developers as it can be reused in different context and models with the clear semantics of imperative statement to to bottom processing. This is the style Kotlin is already trying to promote for continuations and nullability’s special syntaxraulraja
08/24/2019, 12:10 PMfun <B> F<A>.flatMap(f: (A) -> F<B>): F<B>
in order to compute over their continuation or effect being modeled. This would not impose lang syntax changes since the for
block can also be accepted as an expression. And for in
already works observing structurally the Iterable like methods on a type:
val x : Observable<Int> =
for {
a in ob1
b in ob2(a)
a + b
}
The current alternative is callback hell or the coroutines hack Arrow does to implement immutable multi shot continuations.
val x : Observable<Int> =
ob1.flatMap { a ->
ob2(a).flatMap { b ->
...
}
}
This feature would bring imperative syntax to all types that do things like continuations, multi-shot continuations etc since it transparently rewrites imperative binds underneath to the chains of flatMap calls and will immediately unlock List comprehensions and generators that work with for in
. Also it would lift for
to have the same place as when
does allowing it both in statement and in expressions position and making the lang more consistent with blocks returning Unit and supporting statements or expressions when applied. This also opens the Kotlin lang to a broader audience of developers which are used to this basic feature in many other langs that are relevant today in the industry.PhBastiani
08/24/2019, 10:05 PMfor <-
syntax is perhaps more understandable !
Other point : the non-use of 'yield' à la Scala do not allow the chaining of more than one for-comprehension… Is a yield
syntax breaks the retro-compatibility ?
However, the non use of yield
makes the syntax more readable 👌raulraja
08/24/2019, 10:08 PMraulraja
08/24/2019, 10:09 PMraulraja
08/24/2019, 10:09 PMPhBastiani
08/24/2019, 10:13 PMfx
needs to be improved...raulraja
08/24/2019, 10:37 PMraulraja
08/24/2019, 10:42 PMraulraja
08/24/2019, 10:43 PMraulraja
08/24/2019, 10:46 PMstojan
08/25/2019, 11:48 AMgabib
08/26/2019, 5:50 AMelizarov
08/30/2019, 10:57 AMflatMap
, nor rewriting them with with Scala-like for { ... }
comprehensions as @raulraja had suggested. Kotlin almost has everything to write clean, readable code. You can rewrite this example as:
val x : Observable<Int> = flow {
ob1.collect { a ->
ob2(a).collect { b ->
emit(a + b)
}
}
}.asObservable()
One thing is missing, though, for even better code — ability to use for
instead of collect
and write:
val x : Observable<Int> = flow {
for (a in ob1)
for (b in ob2(a))
emit(a + b)
}.asObservable()
elizarov
08/30/2019, 11:33 AMfor
you have in your code.raulraja
08/30/2019, 11:58 AMelizarov
08/30/2019, 12:37 PMflow { for(a in ...) for(b in ...) .... emit(result) }
Scala
for(a <- ...; b <- ...; ....) yield { result }
Kotlin is indeed slightly more verbose:
• Each for
loop has to be explicit
• The result type of flow
has to be explicit
Both of those differences are completely inline with Kotlin’s explicitness philosophy.elizarov
08/30/2019, 12:41 PMUberto Barbini
08/30/2019, 3:36 PMelizarov
08/30/2019, 4:34 PMelizarov
08/30/2019, 4:38 PMfor(...) yield { ... }
in Scala (for
, Carl!) for a single-value thing like a Future
just runs contrary to everything we keep dear in Kotlin.Uberto Barbini
08/30/2019, 6:02 PMUberto Barbini
08/30/2019, 6:05 PMraulraja
08/30/2019, 6:23 PMfor
each time you want to extract A from F<A> is annoying and this is very frequent as many pipelines regardless of the runtime wrapper are frequently defined in terms of flatMap style composition specially in Stream related apps. The compiler already has the notion of for containing a block but it’s a statement not an expression. I still think the natural fit is enabling the in
syntax inside the block so you can destructure the entire computation with imperative syntax and auto lifiting back to the container:
val List<Pair<Int>> =
for {
a in 1..20
b in half(a)
c in half(b)
a to c
}
This is what arrow effects does and the same pattern works for all data types. IMO users find this convenient rather than having a specialized idiom each time they want to bind a flatMap chain as a imperative syntax.raulraja
08/30/2019, 6:27 PMfun <A, B> Flow<A>.flatMap(f: (A) -> Flow<B>): Flow<B>
raulraja
08/30/2019, 6:30 PMf
can’t take place until A
is produced so it strictly models interdependences in the order of values produced by the previous effect where the effect can be anything Deferred, List, Flow, Result, etc.Uberto Barbini
08/30/2019, 6:32 PMraulraja
08/30/2019, 6:32 PMraulraja
08/30/2019, 6:33 PMraulraja
08/30/2019, 6:34 PMLeon K
09/12/2019, 8:44 PMmonad
typeclassraulraja
09/12/2019, 9:17 PMLeon K
09/13/2019, 7:53 PMraulraja
09/13/2019, 9:29 PMfun <A, B> F<A>.flatMap(f: (A) -> F<B>): F<B>
raulraja
09/13/2019, 9:32 PMList
which currently have this shape but does not enforce to use the keyword operator
. This is true for a lot of types in the Java ecosystem if you force them to recompile. I’ll be ok with an operator but then we need a way to define extensions for third parties which use the operator and are considered in resolution. The disadvantage of this approach is that we trust orphan instances and bind means different things based on who’s orphan extension is in scope.