Optimizations

Freestyle provides several optimizations that result in more performant runtimes for applications using Free.

Faster ops dispatching

Freestyle optimizes operations dispatched in the generated FunctionK handlers to increase ops throughput. Traditional Scala hand written FunctionK handlers are often times implemented simply with pattern matching where each case is considered with constructor based patterns as in the example below:

val handler = new (Op ~> Option) {
 override def apply[A](fa: Op[A]): Option[A] = fa match {
    case Op1(x) => ...
    case Op2(x) => ...
    case Op3(x) => ...
 }
}

Freestyle is able to replace this pattern style with a JVM @scala.annotation.switch that operates over a synthetic index in the generated ADTs. This results in a performance boost of about 5x faster ADT evaluation when using Freestyle Handlers over traditional handcoded FunctionK.

This is specially noticeable in apps where each algebras have a long list of operations.

The graph below shows a comparison of evaluating a simple program both with cats.free.Free + cats.data.EitherK + cats.arrow.FunctionK using hand rolled pattern matching vs Freestyle. Higher values are higher throughput.

Not only Freestyle optimizes the evaluation of FunctionK to interpret algebras but it also covers the case where cats.data.EitherK degrades in performance as the number of Algebras increases. cats.data.EitherK is a binary type constructor that when used to combine Algebras as in Datatypes a la Carte suffers from performance degradation as it is a general purpose datatype not optimized for composing monads. This is because as algebras are nested in one side of the Coproduct, getting down to finding the right one is not a constant time operation in the resulting Tree/LinkedList type of structure. Freestyle Coproduct implementation based on iota keeps algebras indexed so when accessed as part of the Coproduct they are found in constant time regardless of the number of algebras your application is composed of.