KIO is a Scala effect system that introduces horizontal composition, moving from traditional vertical composition (G of F of A) to a pending set model where effects S1 and S2 exist without strict ordering, enabling more flexible and composable effect handling. This approach uses pending types (A pending S) to track required effects, with handlers that compositely remove effects from the pending set. KIO maintains interoperability with ZIO and Cats Effect through bidirectional translation and shared runtime infrastructure, while offering advanced features like streaming engines, stateful parsing, and automatic batching that are not available in traditional effect systems.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Beyond `flatMap`: Is Kyo the Future of Scala Effects? by Jonathan Winandy | Scalar 2026Added:
Before we start, I have a quick question for you. Um, is have you touched a a monad in your life? Like, could you raise your hand?
Okay.
And I use Are you still using monads currently uh in the day-to-day?
So, I would say most of you, okay.
Interesting.
>> [snorts] >> Um So, uh yeah, I am. Like, my name is Jonathan.
And uh usually I present subject around data engineering and I My job is to find the right person to talk about something on Scala. So, I've been doing the organization of Scala IO for years.
And recently I became a contributor to to ZIO and so I'm advocating for for ZIO. So, I'm spreading a bit >> [snorts] >> uh what can ZIO do.
So, what is ZIO?
Uh ZIO um in in a big sum up, it's pure functional programming. So, you have to get the idea. It's um tinted by the pure functional programming principle with data types, composition, uh concurrency.
It has a performance objective. Like, you cannot release a a library nowadays that at least don't do as good as most most benchmark compared to the two major solution in the market, which are um ZIO and Cats Effect.
It's competitive with Loom. Uh we we can talk about it. It it depends on the benchmark and some of us would say that actually don't even matter at the end uh because you the IO would matter.
Like, not the scheduling of the IO.
And it has a lot of tools and it connect with the ecosystem.
It has direct syntax. It has uh complete compatibility with ZIO and Cats Effect.
It has a HTTP uh tools, parsing, and you could also build your own tool inside Q.
So, where did Q come from? It come from um sabbatical work of Flavio. And so, Flavio, we know Flavio from uh previous libraries in the Scala ecosystem, uh Quill, so the language integrated queries.
I don't know if you if you remember like in in 2012, we started to have macros announced in Scala to do these kind of things in the language like like integrated queries like uh Link Q.
And so, Quill is able to do that.
Uh there is monad less, uh syntactic sugar for monad.
And on on top of this sabbatical work, uh we have contributions from other that are that have contributed to other effect system like Adam, uh John, uh Jason Pickens, and Kit Langton.
The core idea of Q is to move from vertical composition, so G of F of uh A of F of G to A um that uh has a pending S1 and S2, so that need the effect S1 and S2 and S2. And there is no order between those two.
So, the plan for today is to have put on the short program in Q, um talk about the um the planning types, the on layers, uh explain a bit the suspension mechanism just a bit, um the comparison with uh ZIO and Cats Effect and also the outside. And then we can we can wrap that up.
Okay. So, a short program in Q. To start with, you if you're using Scala actually, uh use a a recent version of Scala, so 3.7 currently.
And you you use it as a dependency.
You need when you work with Q to have extra compiler options. So, value discard, non-unit statement. You will find those compiler options for most of the monadic effect systems in Scala, as well as turning those warning into into errors. Um because that would lead to part of the program to be discarded if you if you don't do that.
The latest version of Q is is 1.0 RC1, but we have a RC2 coming very soon.
That is ready for Scala 3.8.
And a complete program would be that.
So, you have a couple of lines.
In this case, it's in the direct syntax.
So, you expect express a program as a value. So, it's the direct line for each line console.print line.now.
So, it's the async await syntax.
And then you wrap that up with it into a main, and that's it. So, you have a Q program.
If we do it in the regular syntax, that would look like that.
So, we could use combinators.
But the essence of of it is you have a couple of lines. For each line, you are doing an effect or a computation. You chain them using and then. So, it's previous computation and then the following computation.
And then you run that program.
So, you follow like you you would be very similar to what you would do in uh ZIO and Cats Effect.
So, a quick note about the direct block.
Uh thanks to the work of Ruslan, uh we are able to have this direct syntax, which remove a lot of the uh syntax element when you need to work deep on the structure or to combine effects together.
So, you are able to express a program as bit of an imperative style, but it it's a pure uh functional composition.
And that works well in nested structure as well as you could define as as controls and and so on.
So, that makes some of the programs uh more readable and and less like in the Falcon container or flat map style.
Um however, it's completely optional.
It's a it's a syntax on top of Q. And that's the part of Q um I'm mostly contributing to, like uh removing uh issues and improving ergonomics, which is uh so far done for now.
So, the the question we we have to ask ourselves today is, does Q work with AI?
So, currently, out of the box, it's not so bad.
Uh the AI is not trained to work with Q, so Q is is a bit new for the for any uh frontier model.
However, with a bit of context and a bit of example, uh it works fine. And since the inference is working in Q, the AI don't have to uh type for you. It could just infer the type and and that works.
So, let's take a look at the building blocks. So, pending types and and less.
So, the main building block of Q, what you see as a type, is A pending S.
So, that mean A that is waiting for S to be resolved, so we have the value A.
So, to give you comparison, if you have a single value A, that would be A uh pending any. So, pending is a contravariant, so any would be um the like you don't need anything.
In the case of either try or try either, you would have A um in the context of abort. So, that mean that you have a piece of control flow, you need to manage um to uh manipulate the value A.
So, you can directly manipulate A, so as you would do with monads, or you can manipulate the whole context.
For future, you have async.
And if you have a future of either, this is where I start to be interesting.
You have async and a board of each.
They are It's not one in the other. They are side by side.
So, that's a value proposition like effect rotation.
And the horizontal composition.
And if you take a look at the a capture checking, you have a cancel would turn into a board of E.
So, since you're having exposed to capabilities, we could define another operator it's just for this talk, but that will remove a lot of parentheses.
Um, but it if that helps you with the bending gap or the domestic goose gap.
So, once once we have these those values, so, um, for example, V A in in async and F, if you want to to work with V, you would just V.map F and that's it.
As you would do with any monads.
Um, if you have a G that is a a function from B to C in a board of string, uh, you just map as well.
So, map and flat map in Q are the same.
And they they collapse the bending set, the set of effects you need to resolve at the end.
So, on the left side, you have async.
On the right side, you have in in G a board of string, and then you concatenate and you put to the set of effects you need to resolve after.
So, that a lot the the vertical composition and the and the lifting we need to do and undo um when we work with effect system and with effect systems.
And then we have the handlers. So, when we have those values, look, A in the context of S1 and S2, we want to remove like those effects.
And to remove those effects per effect, we have the concept of handler. So, in the case of abort, we want to remove abort, so that means that we have to manage the workflow.
I mean the the control flow where you have errors and you have the value.
You you transform your A into some other value that turn the effect into the value. In this case, it would be a result. It's like an either.
And you may or may not introduce a new effect in the process.
So, to give you an example, we have the program, so string in the context of abort error and async. And when we undo the abort part, we have a result of error string in the context of async.
So, we add the effect at the pending set.
And later in the program, we remove those effects using handlers.
And handlers are composable, so you can choose the the type of handling you you want uh when you want to handle those those effects.
So, async still pending at the end.
But uh at least we in this case we manage all the the errors, so the the checked I mean the types exceptions or type errors and also the um untyped one when they are not uh panic.
So, for var, we would have the same thing. So, var inc.
So, we do the update. So, this function will take uh integer in the state, add plus one.
We have a little function that show from int to string. And then we have this little program. So, it's inc and then inc.
And then map the result to to show.
And we have two uh handlers. The first one is we pass the value zero, and then we uh handle the the effects.
So, we get back the value two.
So, we inc twice.
And we have another handler like run tuple, where we pass the value zero, but also get the state um of the var at the end.
So, you have how many as many handlers as you want in your program, and you can compose them.
Uh which is nice in in some cases.
Uh you just want to uh handle the the state. And in in the other cases, you want to get the the last the remaining value.
So, in the big picture, you compose your your programs in small parts.
And you have the A and then all the effects you need about E var S async.
You have the handlers one by one. So, in this case, we first handle the about, and then we handle the state.
Uh it turns into this value. So, results EA in async. And then you pass that to the main, and you just run.
So, you you stack your effects.
Then you remove them.
And then you run the remaining effect.
Usually, it's async and async at the end of the program.
So, the if we go a bit deeper on the building block, we have the concept of suspension. So, how that works is a queue composed with with a lot of suspensions.
So, in the program, when you declare something like a var, that sets three, you create a suspension at this place in the program that will tag what kind of effect you have at this point.
We'll [snorts] set the input. In in this case, the input is set three. So, that just um I will reset the state to the value three. And then the continuation, what you do with uh the rest of the program. And the job of the handler is to provide the logic to go from the input to the continuation.
In the case of var, the logic is to um maintain a state, to update that state with uh the input, and then to continue the rest of the program with the last value of that state.
And if we do with update, uh it it would be the same kind of suspension. Uh the tag would be uh var of int. The input would be update. And the continuation is what we do with um the uh rest of the suspension.
So, the job of the handler is to connect the input and the continuation.
And you can have the handler you you want in this case. So, the the program is stopped somehow. You just open a frame.
And you have the input, the continuation, and you do what you need to do in between.
Which is very composable at the end because you have could open frames, close them, and you want to make it as efficient as possible.
So, there is a lot of inlining in the process.
So, the principal effect you would see is var, emit, that produce a value like a log or part of a stream.
About for the control flow.
Environment to pass value to your context.
So, that would be like your reader.
A choice.
Choice that is doing multi-shot.
But, multi-shot is debatable. Like, do you really do we really need multi-shot?
And the use case of multi-shot are usually very advanced. But, that's very good to actually test you have composing effects because if you pass a test that have choice in it, usually you have done things right.
So, let's talk about the ecosystem.
The interrupt with ZIO and Cats Effect.
>> [snorts] >> So, if you go from ZIO to KIO, um you can translate your type directly back in KIO. And you have two modules.
Um KIO ZIO and KIO scheduler ZIO, uh which is a ZIO runtime based on KIO.
That's how it's done. So, you do the the interrupt on the value, and you can change the the scheduler to as the same scheduler in ZIO and KIO, and that and that works.
So, the layers supported, there is a macro, and that stream time into a stream.
But, you you see like the the the types in ZIO. So, you have three type parameters.
And in KIO, you you can reduce and increase the pending set.
So, uh you have as many as you want, and you can be more precise in some case. In in ZIO, you have to work with the ZIO type. In KIO, you don't If you don't need it is async, you you are not in async.
From uh Cats Effect FS2 to Kio, uh you have the the same interoperability.
So, you you have a better bi-directional interop on IO, and you could use Kio as a scheduler for Cats.
And the the reason that everything is working with ZIO and Cats Effect is because of the design of ZIO and Cats Effect. They were designed at the beginning to be modular, so you can just uh interop with them, and it's part of the design of uh both ZIO, Cats Effect, and as well as as as well Kio at the end. Those library were having run time you can change. So, it it makes it easy to to interop.
In the case of Stream, um what what's happening in Kio is from a algebraic effect perspective, a stream is just an emit.
So, it's just a new effect. So, you don't have a full library for FS2, you just have um a new effect and combinators on top of it, and that's enough to have FS2 capabilities in um in Kio. So, there is a talk about that from Adam on um verifying stream composition.
That that works except the resources. We need an update in the kernel to manage the resources as good as what FS2 is doing.
And then for Cats Effect, and that concerns me a lot at work cuz it's what I use currently. Like, I have libraries in Cats Effect and a new code base in Kio, um we have Tagless Final in Kio.
>> [snorts] >> And the Tagless Final in Kio is uh actually some something that that is more uh human-friendly, and that would fit the criteria criteria of uh people that have been critical about Tagless Final in the past.
And that just work. Like there is an article about that.
You You can enter up the existing Tagless Final.
Use Use it as an effect.
And And resolve it.
And the type inference work.
That mean that this part like string and And this part is is You could remove that. The type inference would would give you the type.
Which makes Tagless Final a lot more easier to to work with.
>> [snorts] >> It's just an effect called Tagless or use.
And um And that's it.
So you don't have to to change anything you've been doing in Cats Effect.
You just use Q and use this kind of effect to use the code and and combine the effect.
So, let's go to the last part.
And um I know a lot of you have have raised the hand about monads.
But that's not the current subject.
In the Scala community for the the past 2 years we talked a lot about direct styling and capture checking.
And we we compare both of them for good reasons. They They push Scala 3 ergonomics to the max.
Uh direct style is using context function.
Uh Q is using a pending set. And if you take a look at at both definition from a bit from afar like they It's not like they look the same. It's just the difference is Q is lazy by default. And direct style is eager by default.
And but you we still use Scala and we try to have the best uh ergonomics on top of Scala 3.
>> [snorts] >> However, comparing the two or often uh could lead to shortcuts.
And what happened in the past few years is we tried to convince ourselves we could do uh functional programming in the context of direct style. And if I give you an example, I'm I'm not even sure that most of you would know uh what is the error currently uh on the slide.
Like which side is the correct side and which uh side could lead to unsafe behaviors in your program.
Which is which won't be a problem at all once capture checking is activated.
And um so what happened in the conversation we have when we compare the two is we have a lot of of discussions that um doesn't have the uh principle and also the the safety that we require for uh pure functional programming.
So in this case um the left side would compile in in direct style but would be completely unsafe.
Unless you use capture checking. And if it compiles that means it's safe. Like there are cases where that would compile and it's safe. And the other other parts is self safe for most of the times it compiles.
So my point here is I have to remind you like in Scala 3 we have capture checking activated. We cannot use this structure and structure like ZIO [snorts] with just direct style. We need to build something like ZIO and that would be redoing the work ZIO is doing in Scala 3.
Because you need to to hide that in your opaque type and to make sure some of the constructions are not using the implicit resolution.
So what we happen is once the capture checking is less experimental, well, I hope as soon as possible, cuz that's amazing, uh we could integrate the text extension in Kio.
We could increase by a lot the safety and performance of Kio.
Uh once you have capture checking, you can do more aggressive inlining in Kio.
You can have stronger guarantees in the in the kernel as well.
So, that would be a very interesting.
And we could maybe open the direct style in Kio.
So, it started a bit as a joke uh between contributors, uh but a lot of the building blocks in Kio are written in direct style for performance reason.
And they are not open to the users because I would expose to unsafe constructs, but once there is a uh capture checking activated, we could definitely open that path.
Okay.
So, could capture checking replace Kio?
That would be great.
And that would mean we would stop doing uh open source contributions and uh and and pulling our hair in in the compiler error and so on.
But it would take a couple of talks, like maybe a full afternoon, to present all the effects from Kio.
Like we we cannot really let's scratch the surface. We have to talk about the um yeah, isolate boundaries, what happen when you use a var in the context of async computation, uh what are the effect composition, what you can build on top of Kio, like for example, tagless is one of the example I I talked earlier. It's only like four to five lines of code to to build a tool for tagless final, but understanding what's going on would take more time, and would need like a lot more effort to to explain and and to do this advertise for it.
So, that are part of the effect that are already available in in Q.
In blue, you have what you would got from classical effect system. And in in red, that usually stuff you you will mostly find on in Q for now.
We can maybe like uh for example, they are re-rewrite for streaming engines. So, for example, emit a is would be maybe more popular soon, but so far like having a streaming engine as emit is only in Q.
So, you you can build on top of those effects and decompose.
And that mean that for example, we have a parcel in Q that is very nice and can do a lot of stateful parsing and backtracking.
Uh we have batching. Um so, if you have an API that you could group the queries together, you can have automatic batching of that API and so on and so on.
So, the conclusion.
If you are interested and you have a little bit of bandwidth, I currently with the emergence of AI, we don't have bandwidth anymore. Like uh there is so much stuff to read.
Um Q it could could be worth exploring.
Um if you want like advanced composition and you want to to check on the performance.
And if you are already invested in in Zio and Cats Effect, um it's not a start over. There is by the by the internal interrupt, you could even define stuff in uh in Q and use them in in Zio.
Um it lets you capitalize on all your code base. Everything you've written so far can be used and should not be changed. They they should be used as if.
And uh don't hesitate to ask like if you have any questions. Like we are there for that.
And wanted it to to provide inputs like if we have thought of of anything of this kind of use case.
Thank you.
>> [applause]
Related Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 viewsโข2026-05-28
How agent o11y differs from traditional o11y โ Phil Hetzel, Braintrust
aiDotEngineer
450 viewsโข2026-05-28
Re: ๐ฃ๏ธ๐theprophedu๐2026 GST 103 CLASS (E-EXAM REVISION)
theprophedu
636 viewsโข2026-06-04
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation๐ฏโ
LearnwithSahera
1K viewsโข2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 viewsโข2026-05-29
Search Algorithms Explained in 60 Seconds! ๐ค๐จ
samarthtuliofficial
218 viewsโข2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 viewsโข2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 viewsโข2026-05-29











