If Arrow computations are based on suspending func...
# arrow
j
If Arrow computations are based on suspending functions, how can I conceptually translate "a list of effectful actions" into this suspension-based system ? (like e.g. here https://wiki.haskell.org/IO_inside#Example:_a_list_of_I.2FO_actions) And if that question may be irrelevant or doesn't make sense here (or may even be trivial, because a list of functions can surely be created in Kotlin), I guess my underlying question is: What are the specific type-level- or runtime-level-guarantees (or advantages) in this style of computation ? What is nice and obvious is: datatypes (like Either), monadic binding blocks, concurrency-helpers (like parTraverse..., scheduler etc.), cancellation (but we get this anyway from kotlinx-coroutines, or?) But I think there is more to be said about this (and in a clearer way than I tried in an ad-hoc style here 😅), and I think if there is, it should definetly stand out in a to-be-created documentation of a new arrow-release. (just my 2 cents, but I think other prospective 'end-users' ... aka application developers... probably have similar questions.. ?)
p
I didn’t quite understand the question. The guarantee is that sequencing and concurrency is respected
which already is a big one, some places like Javascript can’t assure it
s
If you have
val x: List<suspend () -> Unit>
which is the same as
[IO ()]
you can define.
Copy code
suspend fun <A> List<suspend () -> A>.sequence(): List<A> =
 map { it() }
Which is the same as
sequence :: [IO a] -> IO [a]
. Similarly you can define
sequence_
traverse
etc. Those functions are still missing from Arrow Core tho.
This is possible due to the
inline
feature of Kotlin
j
@simon.vergauwen thx, works fine
Copy code
typealias Action<A> = suspend () -> A

fun createFetchAction(uuid: UUID): Action<Either<Nel<String>, String>> =
    { "result for $uuid".right() }

suspend fun programWithActions() {

    val uuids = listOf(UUID.randomUUID(), UUID.randomUUID())

    val acts = uuids.map { createFetchAction(it) }

    val result = either<Nel<String>, String> {
        // execute something from the list of IO actions
        acts[0]()()
    }
    println(result)
}
@pakoito "The guarantee is that sequencing and concurrency is respected" If you could explain this a bit more, it might actually answer my question. Is it mainly about monadic bind and structured concurrency with cancellation ? Don't get me wrong: I don't question the elegance or usefulness here, I just want to really dig the main points/advantages. Another thing, more or less unrelated: In FP, composability is key at some point... but what can be composed inside an
either
computation (0.12.0-SNAPSHOT) is not discoverable from the types anymore. I have to look it up in the interface for EitherEffect, but that I guess, is just a different mindset for someone coming from Haskell for example...
p
Sequencing means that A -> B happens strictly in that order
sounds silly, but in eager frameworks
Copy code
async { println("A") }
async { println("B") }
B may be printed before A
that’s ofc the most contrived example
the most common one is with state
Copy code
var a = 0
async { a += 1 }
async { println(a) }
a could print 0
this used to happen on JS all of the time with promises. Pseudocode:
Copy code
new Promise({ listenToEvents() })
new Promise({ sendEventTrigger() })
the subscription may happen after the event was sent, thus not listening and acting on it
they fixed it with
then
, and then with async/await
Copy code
new Promise({ listenToEvents() }).andThen({ new Promise({ sendEventTrigger() }) })
Copy code
async function doEvent() {
  await listenToEvents();
  await sendEventTrigger();
}
concurrency is the opposite, the guarantee that if you ask something to happen regardless of order, it does
parallelism for example, that if you ask something to happen on a scheduler, it does
(up to the scheduler’s implementation, i.e. Dispatchers.IO may schedule multiple things concurrently on the same thread 🤷‍♂️)
once you have those two you can build other primitives, like resource management
if acquire resource happens before use resource, and release resource happens afterward
and it always does
cancellation can be summarized as inserting a stop flag check at every suspension point, regardless of if it’s sequenced or concurrent
j
thx for your many pointers, currently I am digesting the "/next" documentation too, I just need some time to wrap my mind around this whole system. Maybe with some more understanding, I may be able to make my questions more concrete