Saturday, 5 September 2026

Kyo vs cats-effect: promising numbers and one big surprise

Kyo vs cats-effect: promising numbers and one big surprise

I find Kyo one of the most interesting projects in Scala right now. I get the feeling that some of the complexity we accept might not be necessary.

That feeling is easy to get excited about. I wanted to see some numbers too. This is the first post in a series about Kyo and cats-effect, starting with a small synthetic war.

I compared cats-effect 3.7.1 with Kyo 1.0.0-RC6, using fs2 3.13.0 on the cats-effect side for streaming.

Getting a fair comparison took more work than I expected. Two methods can look equivalent while doing different things with batches, buffers, or failures.

The suite passed 984 correctness checks. I ran JMH with three JVM forks and identical heap settings on one Mac with JDK 25.

Both sides use the same worker strategy and stream batch boundaries. The success-collection row uses parallel attempts and filtering, rather than native Async.gather. The stream rows do not directly compare parEvalMap with mapPar.

These are selected results with very little work per element. Ratios compare mean throughput; CE means cats-effect.

ConstructionHigher throughput
Uncontended permit acquisition/releaseKyo ~5.02×
Queue-backed stream batchesKyo ~3.70×
Sequential fiber spawn/joinKyo ~2.75×
Parallel stream batchesKyo ~1.93×
Integer queue, one producer and consumerKyo ~1.45×
Bounded workersKyo ~1.13×
Parallel attempt and success collectionConfidence intervals overlap
Left-associated suspended binds, depth 10,000CE ~1,895×

The permit, queue, and fiber results make Kyo worth a closer look. Around 5× the throughput for uncontended permits and 3.7× for queue-backed stream batches are encouraging numbers, even for these small workloads.

A little CPU work changed the results too. Bounded workers went from a small Kyo lead to CE ahead by about 2.22×. Kyo’s lead in parallel stream batches shrank to about 1.11×.

Then there’s the last row. CE handled the 10,000-step left-associated bind chain at about 1,895× Kyo’s throughput. Kyo allocated about 1.6 GB per complete chain, versus roughly 943 KB for CE. Making the chain ten times longer increased Kyo’s allocation about a hundredfold. That points to quadratic scaling in this case, and it needs a closer look.

These results make me more interested in trying Kyo. There’s enough here to justify a small application experiment, and a specific weakness to investigate along the way.

This is one machine and a set of small workloads. Cancellation, resource safety, and real I/O still need their own testing.

Source, full results, confidence intervals, and reproduction commands.

If you spot an unfair comparison, please point me to the code. I’d like to get it right.

Thursday, 28 February 2019

Aux pattern in 5 minutes

Every-time using everytime shapeless.Generic that returns shapeless.Generic.Aux
I was wondering why the construction
type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }
is needed?

If you are impassioned and want to save a time: AUX pattern is wrapping abstract type member into generic parametrisation to solve compiler limitations.

While the Scala's abstract type members are preferred to generic parametrization sometimes there are the problems with making the code compilable.
Lets revisit Type level programming for Streams and start from the case when Aux pattern isn’t needed:

import cats.Show

implicit val requestFunc = new Function[Int, String] {
  override def apply(in: Int): String = s"hello $in"
}
def process[Req, Resp](request: Req)(implicit func: Function[Req, Resp], s: Show[Resp]): String 
  = func(request).show

Our process function just applies the func to request and then using Show type class to represent the result value. Show and func are implicitly injected according to Req and Resp types.

Lets rewrite using abstract type member:

trait Request[Req] {
  type Resp
  def response: Resp
}

implicit def string2Sting = new Request[Int] {
  type Resp = String
  override def response: String = "Hello"
}

def process2[T](value: T)(implicit request: Request[T], m: Show[request.Resp]): String = 
 request.response.show

Method process2 isn’t compilable because we are trying parametrise Show with request.Response type:
error: illegal dependent method type: parameter may only be referenced in a subsequent parameter section

To fix this we introduce proxy type (Aux pattern).

type Aux[T2, B2] = Request[T2] {type B = B2}

And changing the method to:

def process3[Req, Resp](request: Req)(implicit aux: Aux[Req, Resp], m: Show[Resp]): String = 
aux.response.show

As a result we create Aux type that wraps abstract type member into generic to overcome Scala's compiler limitation!

Tuesday, 19 February 2019

Switch career to machine learning specialist

There is continues hype around AI. As this hype stream is still far from been over and predictively is going to grow in time it's natural for the software engineers to pry what is happening in the area of AI Engineer Jobs.

A lot of us been career switchers - got non IT education math or engineering and end up in IT industry as software engineers. Exceptional professions like 3D engine developers etc always have been demanding to heavy math background.

What is about ML - there are many courses/books like learn it in N days. Career switchers are used to get an experience driven into new background with consistent feeling lick of knowledge.

Does this skill help with diving into ML? My opinion it isn't - before starting ML courses it's mandatory to learn/refresh match background - here is graph for studying ML from scratch:




Tuesday, 11 December 2018

Hylomorphism in 1 minute

Hylomorphism is  Catamorphism compose to Anamorphism function.

It's presented as 

Hylomorphism = catamorphism(anamorphism(x))
 or
Hylomorphism = fold(unfold(x))

Basically it constructs (unfold) complex type (like trees, lists) and destructs (folds) back into representing value.

For example to get factorial from N we can 
a) unfold N to list of (n),(n-1),..,0
b) fold with prod function the list from previous step.

And possible Scala example implementing function for getting factorial from N with help of previously introduced list's Catamorphism and  Anamorphism is:

Anamorphism in 1 minute


An Anamorphism (from the Greek ἀνά "upwards" and μορφή "form, shape) over a type T is a function of type U → T that destructs the U in some way into a number of components. On the components that are of type U, it applies recursion to convert theme to T 's. After that it combines the recursive results with the other components to a T , by means of the constructor functions of T .

For example if we want to build List(N, N-1, …, 1) from N we would use anamorphism from Int to List[Int]. It’s the opposite operation to fold - unfold.


Catamorphism in 2 minutes


Catamorphism (κατά "downwards" and μορφή "form, shape") on a type T is a function of type T → U that destruct and object of type T according to the structure of T, calls itself recursively of any components of T that are also of type T and combines this recursive result with the remaining components of T to a U.

And it's just an official name of fold/reduce on higher kinded types. 

For example getting the prod from list of integers is Catamorphism on the list:


Catamorphism in programming can be used to fold complex structures (lists, tress, sets)  into their representation via different type. As an example list catamorphism can be described as


And if we want to fold the list into prod from elements we would use:


Tuesday, 4 December 2018

Isomorphism in 3 minutes



An Isomorphism (from the Ancient Greek: ἴσος isos "equal", and μορφή morphe "form" or "shape") is a homomorphism or morphism (i.e. a mathematical mapping) that can be reversed by an inverse morphism. Two mathematical objects are isomorphic if an isomorphism exists between them.
Isomorphism between the types means that we can convert T  →  U and then U → T lossless.

For example Int to String conversion is sort of isomorphism, but not all the possible values of String can be converted to Int. For example when we try to convert “Assa” into Int we get an Exception. If we want to use identity element for example 0 for any non-number String - Int2String won’t be Isomorphic anymore.


The real isomorphic mapping from String to Int can be done via co-algebra (list’s co-algebra):


You’ve probably generated the tons of Unit tests for  JSON → String and String → JSON isomorphism prove.

Singleton types are always isomorphic to itself, for example type Unit has only one set’s member Unit or () and it’s always isomorphic to itself. Scala compiler has a special flag -Ywarn-value-discard that checks method returns type may break isomorphism rule (make sense only for non side-effect calls).



Currying functions are isomorphic to each other:

Function1 in Scala doesn’t have the curry method and that is why curry isn’t isomorphic for all the Scala’s functions - but it could be if curry from Function1 returned Function1[T, Function1[T1, U]].


Isomorphism as applied to web development means been able to render pages on the both server and client sides. It’s used in context of NodeJS ecosystem because of been able to reuse libraries/frameworks in backend and frontend.