Kyo uses an implicit conversion to automatically lift plain values into computations. Itâs an important usability improvement for its monadic APIs so users donât need to distinguish between map
and flatMap
, which is a common point of friction when people are learning to use monadic APIs. Let me elaborate on how we got to the current design since it might be relevant for this discussion.
In earlier versions, the conversion used to be provided by the compiler via a type bound. Similarly to the into
opaque type proposal, the compiler itself would consider a type A
as a subtype of A < Any
:
// note `>: A`
opaque type <[+A, -S] >: A = A | Kyo[A, S]
We eventually migrated to using an implicit def
conversion to handle a problematic edge case. With the type bound, the compiler automatically nests computations when thereâs a mismatch in pending effects:
// given a type that restricts the pending effects
// (IO in this example)
def test1[A](v: A < IO): A < IO = v
// if a computation with a different pending effect set is passed,
// the compiler automatically lifts `B < Choice` to `B < Choice < Any`
// and then widens the computation to `B < Choice < IO` to match
// the expected type given that `S` is contravariant.
def test2[B](v: B < Choice): B < Choice < IO =
test1(v)
To resolve this issue, the type bound was removed and replaced by an implicit conversion. The conversion requires an evidence WeakFlat that functions similarly to NotGiven
but is defined as an alias to null
and uses inline
to reduce overhead. Ideally, we could eventually migrate to an erased NotGiven
evidence when the feature is marked as stable.
At the time, we didnât use Conversion
due to the fact that its implementation canât be marked as inline
, which introduces overhead especially given the lack of specialization in the compiler. The other reason is the requirement of users having to enable conversions explicitly.
Fast forwarding, we recently introduced first-class support for computation nesting. Previously, the library had another evidence called Flat functioning as a hard requirement enforced via a macro that checked that a type was a concrete class type, which avoided soundness issues with nesting during effect handling.
With the recent changes, the library now handles nesting via internal boxing. The WeakFlat
requirement is still present to prevent the edge case with pending effect set mismatches, but effect handling doesnât require a Flat
evidence anymore. Instead, when a generic type A
is lifted by the implicit conversion, the library reflects on the runtime type of the value in order to introduce a boxing wrapper class if necessary, which provides proper support for nested computations without the need for Flat
.
This is a central aspect of Kyoâs design and, if weâre not able to have a similar encoding once implicit def
support gets removed, it would be a major issue for the project.
Looking at the proposals so far, the opaque type into
approach seems to retain the properties we need but it would require changes across the codebase to mark all method parameters as convertible, which isnât ideal.
The initial proposal of into
as a modifier wouldnât be viable for the project given that we use an opaque type for <
, but the direction is more promising. The ability to trigger implicit conversion seems more of a property of the âdestinationâ type and the behavior seems more predictable that way. Avoiding more complexity in method signatures would also be beneficial.