Dont understand how to make flux subscription working in kotlin - spring

I'm new to reactive programming. I expect to see
test provider started
Beat 1000
Beat 2000
in logs but there is only test provider started and no Beat or on complete messages. Looks like I miss something
#Service
class ProviderService {
#PostConstruct
fun start(){
val hb: Flux<HeartBeat> = Flux.interval(Duration.ofSeconds(1)).map { HeartBeat(it) }
val provider = Provider("test", hb)
}
}
////////////////////////
open class Provider(name: String, heartBests: Flux<HeartBeat>) {
companion object {
val log = LoggerFactory.getLogger(Provider::class.java)!!
}
init {
log.info("$name provider started")
heartBests.doOnComplete { log.info("on complete") }
heartBests.doOnEach { onBeat(it.get().number) }
}
fun onBeat(n: Number){
log.info("Beat $n")
}
}
/////
class HeartBeat(val number: Number)

three pretty common mistakes here:
operators like doOnEach return a new Flux instance with the added behavior, so you need to (re)assign to a variable or use a fluent style
nothing happens until you subscribe() (or a variant of it. blockXXX do also subscribe under the hood for instance...)
such a pipeline is fully asynchronous, and runs on a separate Thread due to the time dimension of the source, interval. As a result, control would immediately return in init even if you had subscribed, potentially causing the main thread and then the app to exit.

In your code lambda from 'doOnComplete' has been never called, because you created infinite stream. Method 'doOnEach' as 'map' is intermediate operations (like map in streams), its doesn't make a call.
And you have another mistake, reactive suggests "fluent pattern".
Try this simple example:
import reactor.core.publisher.Flux
import java.time.Duration
fun main(args: Array<String>) {
val flux = Flux.interval(Duration.ofSeconds(1)).map { HeartBeat(it) }
println("start")
flux.take(3)
.doOnEach { println("on each $it") }
.map { println("before map");HeartBeat(it.value * 2) }
.doOnNext { println("on next $it") }
.doOnComplete { println("on complete") }
.subscribe { println("subscribe $it") }
Thread.sleep(5000)
}
data class HeartBeat(val value: Long)

Related

Kotlin coroutines within a Spring REST endpoint

Given a REST endpoint and two asynchronous coroutines each returning an integer, I want this endpoint to return their sum. The two functions (funA and funB) should run in parallel, in such a way that the whole computation should take ~3secs.
I'm using SpringBoot 2.6.3 and Kotlin Coroutines 1.6.0. Here is my attempt:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
#RestController
class Controllers {
#GetMapping(
value = ["/summing"]
)
fun summing(): String {
val finalResult = runBlocking {
val result = async { sum() }
println("Your result: ${result.await()}")
return#runBlocking result
}
println("Final Result: $finalResult")
return "$finalResult"
}
suspend fun funA(): Int {
delay(3000)
return 10
}
suspend fun funB(): Int {
delay(2000)
return 90
}
fun sum() = runBlocking {
val resultSum = async { funA().await() + funB().await() }
return#runBlocking resultSum
}
}
The problem is that this code does not compile as await() is not recognised as a valid method. If I remove await() the two functions are executed in series (total time ~5 secs) and instead of the expected result (100), I get:
Your result: DeferredCoroutine{Completed}#1c1fe804
Final Result: DeferredCoroutine{Completed}#48a622de
and so the endpoint returns "DeferredCoroutine{Completed}#48a622de".
I want the endpoint to return "100" instead and within ~3secs. How can I achieve this?
You really messed this up ;-) There are several problems with your code:
Use runBlocking() only to use another runBlocking() inside it.
Use async() and immediately call await() on it (in summing()) - it does nothing.
funA().await() and funB().await() don't make any sense really. These functions return integers, you can't await() on already acquired integers.
Generally, using much more code than needed.
The solution is pretty simple: use runBlocking() once to jump into coroutine world and then use async() to start both functions concurrently to each other:
runBlocking {
val a = async { funA() }
val b = async { funB() }
a.await() + b.await()
}
Or alternatively:
runBlocking {
listOf(
async { funA() },
async { funB() },
).awaitAll().sum()
}
Or (a little shorter, but I consider this less readable):
runBlocking {
val a = async { funA() }
funB() + a.await()
}
Also,runBlocking() is not ideal. I believe Spring has support for coroutines, so it would be better to make summing() function suspend and use coroutineScope() instead of runBlocking() - this way the code won't block any threads.

Proper way to call a suspended function in ktor

i would like to start a process is active while my ktor server is up.
suspend fun doWork(){
while(true){
delay(2000L)
printit("I did work")
}
}
I have read that best practice is to avoid runBlocking and GlobalScope.
so i think i should create a custom scope for the coroutine to run in.
(the job is using some db calls so i did this)
val scope = CoroutineScope(Job() + Dispatchers.IO)
i then call it from my main function
fun main() {
val runningJob = scope.launch{
doWork()
}
embeddedServer(Netty, port = 8080, host = "127.0.0.1") {
routing {
get("/") {
call.respondHtml(HttpStatusCode.OK, HTML::index)
}
static("/static") {
resources()
}
}
}.start(wait = true)
}
Is this the proper way to do this? Most of the coroutine examples kind of leave out calling from non suspend functions.
Thank you in advance :)

My MapsActivity crashes without any error message

I think that the problem is I don't know to use well the Coroutines. In Maps Activity you'll see that I access to a PointsDao suspend function that returns a List of objects that I want to use to create marks at my Google Maps Activity.
#AndroidEntryPoint
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding
private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>
private val permissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val mapsViewModel: MapsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {
permissions ->
if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)) {
Log.d("fine_location", "Permission granted")
} else {
Log.d("fine_location", "Permission not granted")
getBackToMainActivity()
Toast.makeText(this, "Necessites acceptar els permisos de geolocalització per a realitzar la ruta", Toast.LENGTH_LONG).show()
}
if (permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)) {
Log.d("coarse_location", "Permission granted")
} else {
Log.d("coarse_location", "Permission not granted")
getBackToMainActivity()
Toast.makeText(this, "Necessites acceptar els permisos de geolocalització per a realitzar la ruta", Toast.LENGTH_LONG).show()
}
}
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
requestLocationPermissions()
}
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera.
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
CoroutineScope(Dispatchers.Main).launch {
val listOfPoints = getRoutePoints()
for (point in listOfPoints) {
mMap.addMarker(MarkerOptions().position(LatLng( point.latitude, point.longitude)))
if (point == listOfPoints[0]) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(point.latitude, point.longitude), 18f))
}
}
}
}
private fun requestLocationPermissions() {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) -> {
Log.d("fine_location", "Permission already granted")
}
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) -> {
Log.d("coarse_location", "Permission already granted")
}
else -> {
requestPermissionLauncher.launch(permissions)
}
}
}
private fun getBackToMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
private fun getRouteId(): Int {
return intent.getIntExtra("routeId", 0)
}
// Gets the points from room repository through ViewModel
private fun getRoutePoints(): List<PointOfInterest> {
val route = getRouteId()
var points = emptyList<PointOfInterest>()
CoroutineScope(Dispatchers.IO).launch {
points = mapsViewModel.getRoutePoints(route)
}
return points
}
This is my ViewModel for this Activity:
#HiltViewModel
class MapsViewModel #Inject constructor(private val repository: RoomRepository): ViewModel() {
suspend fun getRoutePoints(routeId: Int): List<PointOfInterest> {
return repository.getPointsByRouteId(routeId)
}
}
And the Dao:
#Dao
interface PointsDao
{
#Query("SELECT * FROM points_tbl WHERE route_id = :routeId")
suspend fun getRoutePoints(routeId: Int): List<PointOfInterest>
}
My stracktrace error:
Process: com.buigues.ortola.touristics, PID: 27515
java.lang.IllegalStateException: Method addObserver must be called on the main thread
at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172)
at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:49)
at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:84)
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:109)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:171)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:139)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:44)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:31)
at com.buigues.ortola.touristics.ui.MapsActivity.getMapsViewModel(MapsActivity.kt:39)
at com.buigues.ortola.touristics.ui.MapsActivity.getRoutePoints(MapsActivity.kt:123)
at com.buigues.ortola.touristics.ui.MapsActivity.access$getRoutePoints(MapsActivity.kt:31)
at com.buigues.ortola.touristics.ui.MapsActivity$onMapReady$1.invokeSuspend(MapsActivity.kt:85)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
The problem is here in getRoutePoints().
CoroutineScope(Dispatchers.IO).launch {
points = mapsViewModel.getRoutePoints(route)
}
The by viewModels() in your ViewModel property does a lazy load of the ViewModel. As a result, if you access your ViewModel property for the first time when you are not on the main thread, it will try to create it on the wrong thread, triggering this crash. ViewModels must be constructed on the main thread.
CoroutineScope(Dispatchers.IO) means you are creating a coroutine scope that by default uses background IO threads, so this code is run on a background thread.
You should not be creating a CoroutineScope for this anyway, because your Activity already has one that is properly managed by the Activity lifecycle (so it will cancel any in-progress jobs if the activity is closed, to avoid wasting resources).
Also, getRoutePoints() is a suspend function. There's no reason for you to be using Dispatchers.IO here. A suspend function by convention is safe to call from any dispatcher. (It is however possible to write one that breaks convention, but Room is properly designed and does not break convention.)
To fix the crash and run a coroutine properly, you should use lifecycleScope.launch { //.... However, this function as you have designed it won't do what you expect. It launches a coroutine to retrieve a value, but then it immediately returns before that coroutine has finished running, so in this case will just return the initial emptyList(). When you launch a coroutine, you are queuing up background work, but the current function that called launch continues synchronously without waiting for the coroutine results. If it did, it would be a blocking function. There's more information about that in my answer here.
So, you should instead make this a suspend function:
// Gets the points from room repository through ViewModel
private suspend fun getRoutePoints(): List<PointOfInterest> {
val route = getRouteId()
return mapsViewModel.getRoutePoints(route)
}
And your onMapReady function should also be fixed to use proper scope:
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
lifecycleScope.launch {
val listOfPoints = getRoutePoints()
for (point in listOfPoints) {
mMap.addMarker(MarkerOptions().position(LatLng( point.latitude, point.longitude)))
if (point == listOfPoints[0]) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(point.latitude, point.longitude), 18f))
}
}
}
}

How to combine CircuitBreaker with TimeLimiter and Bulkhead?

I have a service that calls a dependency via REST. Service and dependency are part of a microservice architecture, so I'd like to use resilience patterns. My goals are:
Have a circuit-breaker to protect the dependency when it's struggling
Limit the time the call can run. The service has an SLA and has to answer in a certain time. On timeout we use the fallback value.
Limit the number of concurrent calls to the dependency. Usually the rate of calls is low and the responses are fast, but we want to protect the dependency against bursts and queue requests inside the service.
Below is my current code. It works, but ideally I'd like to use the TimeLimiter and Bulkhead classes as they seem to be built to work together.
How can I write this better?
#Component
class FooService(#Autowired val circuitBreakerRegistry: CircuitBreakerRegistry)
{
...
// State machine to take load off the dependency when slow or unresponsive
private val circuitBreaker = circuitBreakerRegistry
.circuitBreaker("fooService")
// Limit parallel requests to dependency
private var semaphore = Semaphore(maxParallelRequests)
// The protected function
private suspend fun makeHttpCall(customerId: String): Boolean {
val client = webClientProvider.getCachedWebClient(baseUrl)
val response = client
.head()
.uri("/the/request/url")
.awaitExchange()
return when (val status = response.rawStatusCode()) {
200 -> true
204 -> false
else -> throw Exception(
"Foo service responded with invalid status code: $status"
)
}
}
// Main function
suspend fun isFoo(someId: String): Boolean {
try {
return circuitBreaker.executeSuspendFunction {
semaphore.withPermit {
try {
withTimeout(timeoutMs) {
makeHttpCall(someId)
}
} catch (e: TimeoutCancellationException) {
// This exception has to be converted because
// the circuit-breaker ignores CancellationException
throw Exception("Call to foo service timed out")
}
}
}
} catch (e: CallNotPermittedException) {
logger.error { "Call to foo blocked by circuit breaker" }
} catch (e: Exception) {
logger.error { "Exception while calling foo service: ${e.message}" }
}
// Fallback
return true
}
}
Ideally I'd like to write something like the docs describe for Flows:
// Main function
suspend fun isFoo(someId: String): Boolean {
return monoOf(makeHttpCall(someId))
.bulkhead(bulkhead)
.timeLimiter(timeLimiter)
.circuitBreaker(circuitBreaker)
}
You could also use Resilience4j's Bulkhead instead of your own Semaphore and Resilience4j's TimeLimiter.
You can stack you CircuitBreaker with bulkhead.executeSuspendFunction and timelimiter.executeSuspendFunction.

Parallel operations on Kotlin collections?

In Scala, one can easily do a parallel map, forEach, etc, with:
collection.par.map(..)
Is there an equivalent in Kotlin?
The Kotlin standard library has no support for parallel operations. However, since Kotlin uses the standard Java collection classes, you can use the Java 8 stream API to perform parallel operations on Kotlin collections as well.
e.g.
myCollection.parallelStream()
.map { ... }
.filter { ... }
As of Kotlin 1.1, parallel operations can also be expressed quite elegantly in terms of coroutines. Here is a custom pmap helper function for lists:
fun <A, B>List<A>.pmap(f: suspend (A) -> B): List<B> = runBlocking {
map { async(Dispatchers.Default) { f(it) } }.map { it.await() }
}
You can use this extension method:
suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> = coroutineScope {
map { async { f(it) } }.awaitAll()
}
See Parallel Map in Kotlin for more info
There is no official support in Kotlin's stdlib yet, but you could define an extension function to mimic par.map:
fun <T, R> Iterable<T>.pmap(
numThreads: Int = Runtime.getRuntime().availableProcessors() - 2,
exec: ExecutorService = Executors.newFixedThreadPool(numThreads),
transform: (T) -> R): List<R> {
// default size is just an inlined version of kotlin.collections.collectionSizeOrDefault
val defaultSize = if (this is Collection<*>) this.size else 10
val destination = Collections.synchronizedList(ArrayList<R>(defaultSize))
for (item in this) {
exec.submit { destination.add(transform(item)) }
}
exec.shutdown()
exec.awaitTermination(1, TimeUnit.DAYS)
return ArrayList<R>(destination)
}
(github source)
Here's a simple usage example
val result = listOf("foo", "bar").pmap { it+"!" }.filter { it.contains("bar") }
If needed it allows to tweak threading by providing the number of threads or even a specific java.util.concurrent.Executor. E.g.
listOf("foo", "bar").pmap(4, transform = { it + "!" })
Please note, that this approach just allows to parallelize the map operation and does not affect any downstream bits. E.g. the filter in the first example would run single-threaded. However, in many cases just the data transformation (ie. map) requires parallelization. Furthermore, it would be straightforward to extend the approach from above to other elements of Kotlin collection API.
From 1.2 version, kotlin added a stream feature which is compliant with JRE8
So, iterating over a list asynchronously could be done like bellow:
fun main(args: Array<String>) {
val c = listOf("toto", "tata", "tutu")
c.parallelStream().forEach { println(it) }
}
Kotlin wants to be idiomatic but not too much synthetic to be hard to understand at a first glance.
Parallel computation trough Coroutines is no exception. They want it to be easy but not implicit with some pre-built method, allowing to branch the computation when needed.
In your case:
collection.map {
async{ produceWith(it) }
}
.forEach {
consume(it.await())
}
Notice that to call async and await you need to be inside a so called Context, you cannot make suspending calls or launching a coroutine from a non-coroutine context. To enter one you can either:
runBlocking { /* your code here */ }: it will suspend the current thread until the lambda returns.
GlobalScope.launch { }: it will execute the lambda in parallel; if your main finishes executing while your coroutines have not bad things will happen, in that case better use runBlocking.
Hope it may helps :)
At the present moment no. The official Kotlin comparison to Scala mentions:
Things that may be added to Kotlin later:
Parallel collections
This solution assumes that your project is using coroutines:
implementation( "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2")
The functions called parallelTransform don't retain the order of elements and return a Flow<R>, while the function parallelMap retains the order and returns a List<R>.
Create a threadpool for multiple invocations:
val numberOfCores = Runtime.getRuntime().availableProcessors()
val executorDispatcher: ExecutorCoroutineDispatcher =
Executors.newFixedThreadPool(numberOfCores ).asCoroutineDispatcher()
use that dispatcher (and call close() when it's no longer needed):
inline fun <T, R> Iterable<T>.parallelTransform(
dispatcher: ExecutorDispatcher,
crossinline transform: (T) -> R
): Flow<R> = channelFlow {
val items: Iterable<T> = this#parallelTransform
val channelFlowScope: ProducerScope<R> = this#channelFlow
launch(dispatcher) {
items.forEach {item ->
launch {
channelFlowScope.send(transform(item))
}
}
}
}
If threadpool reuse is of no concern (threadpools aren't cheap), you can use this version:
inline fun <T, R> Iterable<T>.parallelTransform(
numberOfThreads: Int,
crossinline transform: (T) -> R
): Flow<R> = channelFlow {
val items: Iterable<T> = this#parallelTransform
val channelFlowScope: ProducerScope<R> = this#channelFlow
Executors.newFixedThreadPool(numberOfThreads).asCoroutineDispatcher().use { dispatcher ->
launch( dispatcher ) {
items.forEach { item ->
launch {
channelFlowScope.send(transform(item))
}
}
}
}
}
if you need a version that retains the order of elements:
inline fun <T, R> Iterable<T>.parallelMap(
dispatcher: ExecutorDispatcher,
crossinline transform: (T) -> R
): List<R> = runBlocking {
val items: Iterable<T> = this#parallelMap
val result = ConcurrentSkipListMap<Int, R>()
launch(dispatcher) {
items.withIndex().forEach {(index, item) ->
launch {
result[index] = transform(item)
}
}
}
// ConcurrentSkipListMap is a SortedMap
// so the values will be in the right order
result.values.toList()
}
I found this:
implementation 'com.github.cvb941:kotlin-parallel-operations:1.3'
details:
https://github.com/cvb941/kotlin-parallel-operations
I've come up with a couple of extension functions:
The suspend extension function on Iterable<T> type, which does a parallel processing of items and returns some result of processing each item. By default it uses Dispatchers.IO dispatcher to offload blocking tasks to a shared pool of threads. Must be called from a coroutine (including a coroutine with Dispatchers.Main dispatcher) or another suspend function.
suspend fun <T, R> Iterable<T>.processInParallel(
dispatcher: CoroutineDispatcher = Dispatchers.IO,
processBlock: suspend (v: T) -> R,
): List<R> = coroutineScope { // or supervisorScope
map {
async(dispatcher) { processBlock(it) }
}.awaitAll()
}
Example of calling from a coroutine:
val collection = listOf("A", "B", "C", "D", "E")
someCoroutineScope.launch {
val results = collection.processInParallel {
process(it)
}
// use processing results
}
where someCoroutineScope is an instance of CoroutineScope.
Launch and forget extension function on CoroutineScope, which doesn't return any result. It also uses Dispatchers.IO dispatcher by default. Can be called using CoroutineScope or from another coroutine.
fun <T> CoroutineScope.processInParallelAndForget(
iterable: Iterable<T>,
dispatcher: CoroutineDispatcher = Dispatchers.IO,
processBlock: suspend (v: T) -> Unit
) = iterable.forEach {
launch(dispatcher) { processBlock(it) }
}
Example of calling:
someoroutineScope.processInParallelAndForget(collection) {
process(it)
}
// OR from another coroutine:
someCoroutineScope.launch {
processInParallelAndForget(collection) {
process(it)
}
}
2a. Launch and forget extension function on Iterable<T>. It's almost the same as previous, but the extension type is different. CoroutineScope must be passed as argument to the function.
fun <T> Iterable<T>.processInParallelAndForget(
scope: CoroutineScope,
dispatcher: CoroutineDispatcher = Dispatchers.IO,
processBlock: suspend (v: T) -> Unit
) = forEach {
scope.launch(dispatcher) { processBlock(it) }
}
Calling:
collection.processInParallelAndForget(someCoroutineScope) {
process(it)
}
// OR from another coroutine:
someScope.launch {
collection.processInParallelAndForget(this) {
process(it)
}
}
You can mimic the Scala API by using extension properties and inline classes. Using the coroutine solution from #Sharon answer, you can write it like this
val <A> Iterable<A>.par get() = ParallelizedIterable(this)
#JvmInline
value class ParallelizedIterable<A>(val iter: Iterable<A>) {
suspend fun <B> map(f: suspend (A) -> B): List<B> = coroutineScope {
iter.map { async { f(it) } }.awaitAll()
}
}
with this, now your code can change from
anIterable.map { it.value }
to
anIterable.par.map { it.value }
also you can change the entry point as you like other than using extension properties, e.g.
fun <A> Iterable<A>.parallel() = ParallelizedIterable(this)
anIterable.parallel().map { it.value }
You can also use another parallel solution and implement the rest of iterable methods inside ParallelizedIterable while still having the same method names for the operations
The drawback is that this implementation can only parallelize one operation after it, to make it so that it parallelize every subsequent operation, you may need to modify ParallelizedIterable further so it return its own type instead of returning back to List<A>

Resources