Skip to content

Instantly share code, notes, and snippets.

@ahoy-jon
Last active September 7, 2025 10:40
Show Gist options
  • Select an option

  • Save ahoy-jon/e7b80400835a01fd3b3cdb71143d1e7f to your computer and use it in GitHub Desktop.

Select an option

Save ahoy-jon/e7b80400835a01fd3b3cdb71143d1e7f to your computer and use it in GitHub Desktop.
Lifting Values in Kyo

Lifting Values in Kyo

In the Kyo framework, a common and fundamental operation is the transformation of a simple value A into an effectful value A < S. This process, known as "lifting," allows a plain value to be seamlessly integrated into Kyo's effect system. While this can be done explicitly, it is most often handled implicitly by the Kyo compiler, which makes for cleaner and more concise code.

Explicit Lifting

The most direct way to lift a value is by using the Kyo.lift function. This function takes a value and wraps it in a Kyo effect type.

val v1: Int < Any = Kyo.lift(1)

val v2: Int < (Abort[String] & Sync) = Kyo.lift(1)

In the first example, 1 is lifted to Int < Any, meaning it is now an integer value that can be handled within any Kyo effect context. In the second, the value is lifted to a more specific effect type, Int < (Abort[String] & Sync), indicating that it can be used in a context that may also handle Abort and Sync effects.

The < Opaque Type

The core of Kyo's lifting mechanism lies in its definition of the effect type, which is an opaque type alias.

opaque type <[+A, -S] = A | Kyo[A, S]

This definition reveals that a value of type A < S is either a plain value A or a Kyo effect. This union type allows Kyo to represent both a completed, non-effectful value and a pending effectful computation using the same type.

Implicit Lifting: The Compiler's Convenience

Kyo's true power comes from its ability to perform lifting implicitly. This means that a developer can often write code that looks like a normal value assignment, and the compiler will handle the lifting automatically.

val v3: Int < (Abort[String] & Sync) = 1

Here, the integer literal 1 is automatically lifted to the required type Int < (Abort[String] & Sync), eliminating the need for an explicit Kyo.lift call. This greatly improves code readability and reduces boilerplate.

The Lifting Mechanism Under the Hood

The implicit lifting is powered by a set of three different implicit definitions, which can be seen in the Kyo source code.

implicit inline def lift[A: CanLift, S](v: A): A < S = ${ LiftMacro.liftMacro[A, S]('v) }

implicit inline def liftAnyVal[A <: AnyVal, S](inline v: A): A < S = v.asInstanceOf[A < S]

implicit inline def liftUnit[S](inline v: Unit): Unit < S = v.asInstanceOf[Unit < S]

These definitions might seem complex, but their purpose is to provide an optimized lifting process. The liftAnyVal and liftUnit definitions handle primitive value types and Unit respectively, using a simple asInstanceOf cast for efficiency. The main lift macro is then used for all other cases.

A Conceptual Look at the Logic

Conceptually, a non-macro implementation of lifting would look something like this:

def lift[A, S](v: A): A < S = v match
  case v: ? < ? => nested(v) // Handle nested Kyo values
  case v        => cast(v)   // Cast simple values

This conceptual implementation, while easy to understand, would incur the runtime cost of a match expression. Kyo's approach uses a macro to perform this logic at compile time, optimizing away the runtime cost. The macro effectively creates a compile-time pattern match, inlining the appropriate casting logic based on the type of the value being lifted. source

case u:Unit         => cast(u)
case a:AnyVal       => cast(a)
case a:Nothing      => cast(a)
case k:(? < ?)      => nested(k)
case o:OpaqueType   => cast(o)
case c:ConcreteType => cast(c)
case v              => atRuntime(v) // Fallback to a runtime check

This compile-time analysis allows Kyo to handle the most common cases (primitives, Unit, and concrete types) with a direct cast, while only falling back to a runtime check for more complex or unknown types. At present, Scala's transparent inline match is not yet fully leveraged for this kind of optimization, which is why Kyo relies on a macro to achieve this performance gain.


Kyo's lifting mechanism is a sophisticated,macro based, yet user-friendly feature that enables seamless integration of simple values into its powerful effect system. By using implicit conversions and a compile-time macro, it ensures that this essential transformation is both easy to use and highly performant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment