Here's a bit of a crazy idea: "transparent" `value...
# language-evolution
y
Here's a bit of a crazy idea: "transparent" `value class`es (I'm asking this here first to gather some feedback because currently I really don't think this is a good proposal at all lol) Simply, if there's a value class with 1 parameter, you could add a
transparent
modifier to it so that, whenever you have a value of that value class, it acts like if it is of the aforementioned type, but you may still access the functions that the value class itself provides. In a way, this is just a way to provide scoped extension functions, which I admit is practically solved by multiple receivers already, but there's some use-cases that don't quite fit that bill. Let me first just start with a simple example that is solved by context receivers:
Copy code
transparent value class NumericalString private constructor(val underlying: String) {
    constructor(value: Int): this(value.toString())
    ... etc for the rest of the number types
    val doubled: String get() = "${underlying.toDouble() * 2}"
}

fun main() {
    val fortyTwo = NumericalString(42)
    val four = fortyTwo.doubled.lastLetter()
    println(four + fortyTwo)
}
fun String.lastLetter() = last()
Re-writing this with context receivers is messy-ish, but it's kind of doable:
Copy code
interface NumericalStringOperations {
    val String.doubled: String
    companion object Default: NumericalStringOperations {
        override val String.doubled: String get() = this.toDoubleOrNull()?.let { "${it * 2}" } ?: this
    }
}

fun main() {
    with NumbericalStringOperations:
    val fortyTwo = "42"
    val four = fortyTwo.doubled.lastLetter()
    println(four + fortyTwo)
}
fun String.lastLetter() = last()
The issue is, however, that first of all this is not typesafe, since it has no idea if a String is numerical or not. The second issue is a bit niche, which is that what if you want to shadow a function that already exists:
Copy code
transparent value class NumericalString private constructor(val underlying: String) {
    constructor(value: Int): this(value.toString())
    ... etc for the rest of the number types
    val doubled: String get() = "${underlying.toDouble() * 2}"
    // Kind of assuming that no boxing will really happen and that the compiler is smart enough to not force `other` to be boxed
    // The point of this is that "42" and "42.0" should be considered equal
    override inline fun equals(other: Any?) = if(other is NumericalString) other.underlying.toDouble() == underlying.toDouble() else underlying == other.underlying
    fun last(): NumericalString = NumericalStirng(underlying.last())
}

fun main() {
    val fortyTwo = NumericalString(42)
    val four = fortyTwo.doubled.last()
    val doubleFour = NumericalString(4.0)
    println("$four == $doubleFour returned ${four == doubleFour}") // Should be true
}
In fact, this absolutely can't be done with context receivers (right now) because
@HidesMembers
is limited in its usage to a handful of functions, and even if you do hack around the compiler to allow it to work everywhere (which I did try), it still doesn't resolve the shadowing correctly because call resolution with
@HidesMembers
only takes into account normal static extensions and not member extensions. Even if
@HidesMembers
was extended properly to handle this, I still believe that making it a first-class citizen in Kotlin gives it more validity, if you will. So far, the case I've mentioned is basically just a refined type, but this feature can extend beyond that. For example, let's take a detour into functional programming with a simple
Either<A,B>
perhaps like this:
f
What is this pythonic
with:
syntax?
y
Oh I couldn't quite remember what the syntax was from the context receivers proposal but basically this is the bit that I meant:
It is possible to gradually turn 
with
 into a hard keyword to support even more concise syntax for bringing annonymous receivers into the scope in a class:
class Service {
with createServiceContext() // Introduce anonymous scope property
}
And, potentially, the same for the local scope (expanding on Algebraic effects and coeffects example):
fun helloToConsole() {
with Emit { msg -> println(msg) }
hello()
}