Now we have value classes and sealed interfaces. ...
# arrow-contributors
p
Now we have value classes and sealed interfaces. I was wondering if we can find a more performant way to define Union types (before stable Meta) 🧵
Unfortunately, generics can’t be used with value classes 😢 Otherwise we could do something like:
Copy code
public sealed interface Either<out A, out B>

@JvmInline
public value class Left<out A>(public val value: A) : Either<A, Nothing>

@JvmInline
public value class Right<out B>(public val value: B) : Either<Nothing, B>
Until something like this is supported I think I found a potential work around
Copy code
public sealed interface Either<out A, out B>

public interface Left<out A> : Either<A, Nothing> {
    public val value: A

    public companion object {
        public operator fun <A> invoke(value: A): Left<A> = InternalLeft(InternalEither(value))
    }
}

public interface Right<out B> : Either<Nothing, B> {
    public val value: B

    public companion object {
        public operator fun <B> invoke(value: B): Right<B> = InternalRight<B>(InternalEither(value))
    }
}

@JvmInline
public value class InternalEither internal constructor(internal val value: Any?)

@JvmInline
public value class InternalLeft<A> internal constructor(
    private val internalEither: InternalEither,
) : Left<A> {
    override val value: A
        get() = internalEither.value as A
}

@JvmInline
public value class InternalRight<B> internal constructor(
    private val internalEither: InternalEither,
) : Right<B> {
    override val value: B
        get() = internalEither.value as B
}
But I’m not sure if this will work fully, need to run some tests and benchmarks first 🙂 Not sure if the cast would have any overhead 🤔
What do you folks think?
This is another potential implementation:
Copy code
@JvmInline
value class Either<out A, out B> internal constructor(private val value: Any?) {
    val left: A? get() = value as? A
    val right: B? get() = value as? B
}

@Suppress("FunctionName") // constructor
fun <A> Left(value: A): Either<A, Nothing> = Either(value)

@Suppress("FunctionName") // constructor
fun <B> Right(value: B): Either<Nothing, B> = Either(value)

inline fun <A, B, C> Either<A, B>.fold(ifLeft: (A) -> C, ifRight: (B) -> C): C =
    left?.let(ifLeft) ?: right?.let(ifRight) ?: error("WTF compiler?")
But, with this one we would loose the option to use
when
expressions, which is a breaking change 🙃
r
for this to be considered we would need to determine if there is any advantages/disadvantages over the current implementation. Using Any? as the marker of a value class may still result in boxing in most generic functions which after all is almost all of the functions in the either api.
would be interesting to see comparisons on generated bytecode and microbenchmarks to see if reencoding the types and most likely breaking backward compat in such a way makes sense. If its a considerable speed and reduction of allocations it may make sense for the next major version
p
Ah, yeah, I didn’t think about the boxing. I want to add some benchmarks at some point so we can see if things are better or worse after we make changes 🙂