<@U2E974ELT> We are using coroutines to achieve mo...
# coroutines
r
@elizarov We are using coroutines to achieve monad bind syntax in Kategory generalized to all monads and it looks like this for example with Option.
Copy code
Option.monad().binding {
  val a = Option(1).bind()
  val b = Option(a + 1).bind()
  yields(b)
}
//Option(2)
binding
is a Continuation and
bind
is implemented as a
COROUTINE_SUSPENDED
function that unwinds
flatMap
calls until the entire binding is completed as shown here https://github.com/kategory/kategory/blob/master/kategory-core/src/main/kotlin/kategory/typeclasses/MonadContinuations.kt#L53-L61 For this to work we are resorting to a dirty hack which is making the private
label
and
completion
properties visible so we can mutate them accordingly. https://github.com/kategory/kategory/blob/master/kategory-core/src/main/kotlin/kategory/typeclasses/ContinuationUtils.kt Do you know if there is a better way of doing this? Thanks in advance for any pointers in the right direction 🙂
e
That's a puzzler. I don't quite grasp the whole idea on why you need to save/restore them. Can you point to some example code that will cease to work without this saving/restoring of labels? What if I just comment restroing the labels out? What breaks?
r
https://github.com/Kotlin/kotlin-coroutines/issues/72 our impl was based on that idea. From that link:
A Continuation<T> implemented by a state machine modifies its label, so that we cannot run one continuation several times from the same label. The workaround I used: back up the label through reflection before the resume call and restore it once the call returns.
e
Can you just use
for (x in m)
instead of
val x = bind(m)
? The former avoids the need to save/load state altogether, as the compiler becomes responsible for it.
r
@elizarov does
for(_ in _)
support multiple binds or are there plans to do that? Something along the lines of :
Copy code
for (x in listOf(1); y in listOf(x + 1)) yield y // List(2)
If
for
is just looking for
iterator()
I can potentially implement monadic bind in terms of
hasNext()
and
next()
in monads but if a single bind is supported it would result in a nested chain of for loops and I have no way to return the resulting values since
for
is not an expression but a control structure.
Have there been discussion regarding supporting
flatMap(f: (A) -> F<B>)
in the same way it supports
iterator()
so any structural evidence of having
flatMap
on a type allows for comprehensions enabling List generators, etc?
e
Multiple binds with
for
are done like this:
for (x in listOf(1)) for (y in listOf(x + 1)) yield(y)
And it does not have to be an
Iterator
interface. It works by convention. It can be any interface that has
operator fun iterator(): SomeResult
where
SomeResult
has
operator fun hasNext(): Boolean
and
operator fun next(): T
That is how iteration over channels work: You can do
for(x in channel)
where
channel
is neither
Collection
, neither
Sequence
, nor
Iterable
at all
It is Kotlin’s approach to list comprehension & do-notation
r
@elizarov awesome, trying to come up with a prototype. Thansk for the info!
p
@elizarov is for an expression?
r
@pakoito seems like with
yield
it becomes one
@elizarov assuming this:
Copy code
operator fun <A> Option<A>.iterator(): OptionIterator<A> = OptionIterator(this)

class OptionIterator<A>(val fa: Option<A>) {
    operator fun hasNext(): Boolean = fa.fold({false}, {true})
    operator fun next(): A = fa.getOrElse { throw IllegalStateException("next() invoked on empty Option") }
}
How can I implement this function using `for`:
Copy code
fun compute(): Option<Int> = 
   for(x in Option(1))
   for(y in Option(x + 1))
   yield(y)

compute() // Option(2)
Aside from the fact that
yield
requires to be invoked in a coroutine. Do I need to resort to mutation to be able to return something or does
yield
lifts values back into the context of
Option
in this case?
e
You'll have to wrap it all in option builder, like option {}. Does not have to be a coroutine (since there is only one element)
r
ok, would
yield(y)
in that case match the return type expected by the option builder? If say the option builder expects back
Option<Int>
for example would
yield
satisfy that or do builders always need to return
Unit
?
nevermind, I see yield would change the state in the continuation builder and then I can return myself whatever I want from it
thanks for the info @elizarov