new Proposal: Error-able Types (yes, the name suc...
# language-proposals
l
new Proposal: Error-able Types (yes, the name sucks, that should obviously be changed) we have
nullable
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
Copy code
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:
Copy code
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:
Copy code
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:
Copy code
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.
m
Also take a look at the unreleased version of Arrow Fx. Gets very close to comprehensions in other FP languages, so you don't need all the repeated
map/flatMap
statements but it's still doing the chaining.
l
i've seen Monad Comprehensions, theyre great, but i still think that this is the one language-feature that is kind of missing for Kotlin to be the defacto "safe yet readable" language. nearly no other language has the
?
operator, but kotlin does and that singlehandedly makes null-handling a piece of cake. if only this would also apply to exception handling
r
comprehensions are coming with arrow-meta as a compiler plugin and part of FX. They will be rewritten in place to flatMap so it will bypass the suspension system
I think that even a better feature than specialized syntax for all effects like nullability, errors etc is to have union types in kotlin
💯 2
With Union types we could denote the type being A or B without the need for a data type wrapper and much leaner runtime.
l
I agree, true union types would be a great thing,... but i doubt that will ever get added, as people will say "we have sealed classes, you can do unions with them, kinda"
but a naive Union-type implementation wouldn't reaaaly make errorhandling much easier, as code would have to look like this:
Copy code
kt
val maybeSomething: (SomeException | String) = foo()

when(maybeSomething) {
  is SomeException -> // handle error
  is String -> // do something 
}
I dont know how error-handling worthy
map
and
flatmap
methods could work on unions...
i guess you could provide extension functions for Union types
Copy code
fun <T, O> (Throwable | T).map(transform: (T) -> (Throwable | O) = ...
but even that would be complicated...
Copy code
val foo: (Int | String) = foo()

foo.let<Int> { it + 2 }
     .let<String>{ "$it is a string" }
maybe?
r
Copy code
typealias 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
yes you could use it as a regular type
I have implemented higher kinds emulated already with meta with a modified type checker and I know this is possible with compiler plugins but it may require of of course more work than a simple plugin that does codegen
But all phases are interceptable including resolution
l
niceee
r
we are currently working on IDEA support to autogenerate synthetic descriptors for the codegen
l
if you need to get the String or Int value from your StringOrInt type, is the only way to do it with a
when
statement?
r
no, union types could have synthetic members like fold based on the arity
a union type can be composed of N types
Is essentially Arrow’s generic Coproduct but at the type level
it’s a tree so you can fold it and unfold it
it could potentially be recursive if Kotlin accepted recursive type aliases
From union types you could get automatic injectable behaviors for instances of Functor, Foldable, Traversable, Prism and in general any behavior that can be derived from the fact that is a tree and also represents a disjoint union
l
using folds and such for many if not most operations done on data does seem kind of verbose though... I think unions would be a great addition to the language, as would real pattern matching and typeclasses, but I can't really see them being the best solution for simple error-handling cases, as they dooo sound as tough they add a lot of complexity that could throw off many people
r
then what Kotlin needs is comprehensions generalized structurally by the presence of flatMap
because if we add a new syntax for each effect you want to flatten computing over it will never stop growing
l
a
flatMap
operator function /
?.
thingy? that would be great indeed
r
nullability is understandable in my opinion as it’s a great feature over NPE concerns
yes in Arrow Fx that operator is called
!
l
altough that does imply having typeclasses in some way
r
as in the Frank Effect lang
but a new operator could be designated for flatmap
l
the `fx`comprehensions in arrow are great, but they require being in a specific block (which is obviously understandable from an implementation standpoint, i'm really impressed by what arrow is able to achieve syntactically). a flatmap-operator would need to be simmilar to
?
in the way it would be used, to actually be helpful in the readability of code ( at least in the short term)
what do you think about the current way the typeclass/compile-time-dependency-injection-propsal plans to do them? i think theyre proposal is great for DI, but i can't see the Kotlin language actually using them as typeclasses, because they just dont feel typeclassy
r
In the new version of the comprehension plugin they don’t need to be in a fx block
You can destructure bind in place anywhere as long as your current expression returns the wrapped type
then we rewrite all that to a chain of flatMaps
it’s currently in development:
so using the fx block will be optional otherwise you are responsible for exiting the function in wrapped form:
All of these are valid Kotlin with this compiler plugin which rewrites all your binds
!
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 hierarchy
this allows for multishot style continuations which Kotlin can’t support currently
and data types like Observables etc which also include flatMap can be net to their knees to imperative syntax
This includes:
Defferred, IO, Flowable, Flow, Mono, Flux, Observable, List and you name it.
structural monadic comprehensions for all data types is in my opinion a far better and easier to understand construct in a lang than adding one at a time specialized syntax for each monadic data type.
Kotlin already has this concept for iterables via the presence of next
it uses that structurally since your datatype is not forced to subtype Iterable to get the benefit of
for 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:
Copy code
val fa: F<A> = 
  for {
   a in fa
   b in fb(b)
   b
  }
that should work for anything that is higher kinded and has flatMap
and I also plan to bring simplified kind syntax by replacing types at compile time of all AST occurrences of
F<A>
for
Kind<F, A>
so Kotlin users never have to deal with Kind<F, A> ever again
These are all opt in/out features and do not require consumer of libraries to use any of them since it’s all provided via compiler plugins as jetpack compose, serialization and others do where they intercept and modify the compiler phases transforming user code into new synthetic declarations.
the arguments in
for
could be optional and the body changed from an statement to also accept to be an expression when its in an assignment or return
this would introduce no changes in the lang syntax and will be retro compatible and portable to all other Kotlin versions currently maintained. @Leon K I love your intentions but I believe the problem to solve is bigger but I speak on no authority since I don’t decide in the compiler. These are just my wishes and those of many I know in the Arrow and Kotlin community.
l
this is great! I love your solution, and i agree that having a more general solution within the language would be fantastic. I just doubt that it's realistic the Language team will actually add something like this, as it would change the way kotlin is written quite a lot, and make it further away from java. this isn't a bad thing for me, but as kotlin tries to be as easy as possible to migrate to from java, I think that it might be to much for now. It could be possible in a few years, when kotlin doesn't need to convince people that its great by being as easy / even easier than java while still being a lot more powerful.... I will absolutely look into that compiler plugin, as it seems to be a solution for a much bigger problem ;D I just doubt that native integration into the language is realistic within the next few years, seeing that they don't even want to implement simpler features like true pattern-matching with value extraction for language-/compiler-complexity reasons...
r
There is an incredible amount of devs that kotlin would attract from other langs if it had this feature since most langs but java already have a way to comprehend over lists or similar
l
I fully agree with you, i just fear that Jetbrains is focused on replacing java more than on replacing Haskell and the like
r
I think that while Haskell has this feature so does Scala, Python, C# and many others. If Kotlin wants a space in the big data style backend world which is currently mostly Scala treating
flatMap
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 syntax
I’d love to know what people from Jetbrains and the community thinks about a feature like this and if it has a place in the Kotlin compiler or not. This would immediately bring imperative coroutine style syntax to all frameworks in Kotlin like Rx2, etc because all those data types are always based on structurally containing a member like flatMap
fun <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:
Copy code
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.
Copy code
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.
💯 16
👍 4
p
Comprehension is, for sure, a missing feature of Kotlin. I like your proposal 👍 A
for <-
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 👌
r
The current fx blocks shows the yield syntax is not needed and Scala has especulated with removing it all together
This is not comprehension a because comprehensions enforce binds in an order and we can bind here in place anywhere in restricted suspension context like arrow fx does today
This is essentially bringing the coroutines philosophy to all data types that have flatmap
👌 1
p
Interestingly, I had not seen this subtlety. My knowledge of
fx
needs to be improved...
r
Fx uses applicative just which in here we can use the first suitable constructor to eliminate yield
Here is some examples of possible bind styles https://github.com/47deg/arrow-meta-prototype/blob/master/consumer/src/main/kotlin/consumer/comprehensions.kt fx uses overloading the not operator
flatMap would also become an operator
Notice in some of the examples there is no need to use fx. In the proposal for replaces fx and in replaces bind
s
I like the Zero, One, Infinity rule https://en.m.wikipedia.org/wiki/Zero_one_infinity_rule At the moment Kotlin has a special syntax for nullability, it makes sense to generalize it for all effects (and we know that is possible) than adding just a second one
g
I would love to see this as well as KEEP-87 in the language
💯 1
e
Frankly, I’m not a fan of either nested
flatMap
, 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:
Copy code
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:
Copy code
val x : Observable<Int> = flow {
     for (a in ob1) 
         for (b in ob2(a))
             emit(a + b)
}.asObservable()
On a more philosophical side: nested loops are known to make code quite harder to comprehend, so any kind of sugar that lets you write nested fors in a less explicit way is actually counter-productive to readability. It is better to explicitly see each
for
you have in your code.
r
I don't think people would be happy typing for in sequence N times when they have multiple values. Is emit an expression or a statement?. The whole point is that the comprehension is an expression. Currently for can only do statements
e
It is an expression that is not much visually different from Scala’s. Compare: Kotlin
flow { 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.
Just like scala’s for-yield loop can be used for any appropriate type, you can write flow-emit builder for any type of your liking in Kotlin with an option of having a distinct name for different types to make it easier for humans to read (sequence-yield, buildString-append, etc)
u
wouldn't be better to switch from: flow { for(a in ...) for(b in ...) .... emit(result) } to: flow { for(a in ..., b in ...) .... emit(result) } I understand that some people can see little value here (not me) but if anything the second syntax seems more idiomatically Kotlin to me. That could be useful in many other cases without Flow or flatmap
e
Maybe. But again, the fact is that: nested loops are hard, nested loops do not occur often in practice. So making a special syntactic construct is both harmful for understability and gets little “bang for a buck” at the same time.
Various “single-value” monads (either, try, etc) are a different case, though. You don’t need need loops for them, and you should not use loops for them. That fact that you use
for(...) yield { ... }
in Scala (
for
, Carl!) for a single-value thing like a
Future
just runs contrary to everything we keep dear in Kotlin.
u
" nested loops are hard, nested loops do not occur often in practice" -> lots of computations are just nested loops. They are hard so it would be nice to have a little help. My experience with Python for comprehension (Pandas etc.) convinced me that is a real useful feature regardless of monads.
On the other side a little of fun: fun half(x: Int): Outcome<Error, Int> = ... for (a in 1..20) for (b in half(a)) for (c in half(b)) println(a to c) [(4, 1), (8, 2), (12, 3), (16, 4), (20, 5)] 🙂
👍 1
r
having a
for
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:
Copy code
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.
❤️ 1
Coroutines Flows would immediately benefit from this syntax provided the include proof of something like :
Copy code
fun <A, B> Flow<A>.flatMap(f: (A) -> Flow<B>): Flow<B>
❤️ 1
flatMap does not imply a nested loop but rather that the following computation
f
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.
u
Exactly @raulraja My point is that would be not only useful for Monads-pals like us but also for lots of other applications which are processing multidimensional data.
r
For example you could use Try or Either in that chain guaranteeing composition and that one never scapes the container. I think that is powerful because it helps developers understand computation as a generalization for all effects and gives them an encoding they can rely on for all their chains of effects
I think this is useful regardless of monads and FP because it unlocks generators and imperative syntax for all containers regardless if their effect is async or sync
💯 3
without the need for suspension or a runtime, just an AST rewrite that delegates to flatMap
l
I recently Had a New idea regarding this: what if you turned flatMap into an Operator function, and created a Do-Notation based on that? This would be an idiomatic solution to generalize monadic chains in kotlin, without needing to either create fully new Syntax-ideas or needing some Kind of typeclass. Declaring flatMap a Operator function, that is used in monad comprehensions could be a lot easier on the language than needing some Kind of
monad
typeclass
❤️ 1
💯 2
🔝 1
r
I don’t propose we use a Monad Typeclass. My proposal is just what you said but without enforcing the keyword operator so that it automatically is supported foo all data types in the ecosystem that have flatMap. For example List. You can enforce the operator if you let resolution find an extension function called flatMap for the type in scope imported since we would be able to create operator flatMap for missing types.
l
I feel like just having the name of the function dictate that it can be used that way feels a little ugly, i mean thats what kotlin Has Operator functions for.
r
it’s not the name of the functions but it’s structure as soon in the previous comment: in https://kotlinlang.slack.com/archives/C0B9K7EP2/p1566648621078800?thread_ts=1566583440.057700&amp;cid=C0B9K7EP2
Copy code
fun <A, B> F<A>.flatMap(f: (A) -> F<B>): F<B>
If you enforce an operator it’s fine but then you are force to declare proof via ext funs for types such as
List
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.