andylamax
12/01/2023, 9:30 AMtypealias LoggableDatabase = Loggable & Database
fun LoggableDatabase.listAll() : List<Thing> {
log("listing") // from (this as Loggable)
return query("select * from things") // from (this as Database)
}
Would endup being almost close to
context(Loggable,Database)
fun listAll() {
log("listing") // from (this as Loggable)
return query("select * from things") // from (this as Database)
}
This question can be phrased in another way.
e.g. Shouldn't the kotlin team work on intersection types and that will give us both, intersection types and context receivers functionality as well???? Thoughts????Sam
12/01/2023, 9:34 AMandylamax
12/01/2023, 11:45 AMinterface A {}
interface B {}
typealias TAB = A & B // intersected type
class CAB : A,B {} // inherited type
What you just explained fits the description of inherited types but not intersected types. (But again, I think all inherited types do also intersect their respective interface abstractions)andylamax
12/01/2023, 11:55 AMval tab: TAB = getTAB()
val cab: CAB = getCAB()
fun useType(ab: TAB) {
}
fun useClass(ab: CAB) {
}
// then
useClass(cab) // should work
useClass(tab) // should fail ?? not sure
useType(tab) // should work
useType(cab) // should work
edrd
12/01/2023, 12:27 PMfun <T> T.listAll(): List<Thing> where T : Loggable, T : Database { /* ... */ }
edrd
12/01/2023, 12:30 PMLoggable
and Database
andylamax
12/01/2023, 12:36 PMLoggable
and Database
).
With intersected types,Loggable
and Database
can even be final classes (instead of interface), which would not need a user to implement both of them because they are not only limited to interfacesedrd
12/01/2023, 1:42 PMWith intersected types,You mean with context receivers, right? Because with intersection types, the type must implement all specified types, either by inheriting an open/abstract/sealed class or by implementing an interfaceandLoggable
can even be final classes (instead of interface)Database
andylamax
12/01/2023, 6:49 PMtypealias IntOrList = Int | List<Int>
for unions
and something like
typealias IntAndList = Int & List
andylamax
12/01/2023, 6:49 PMFederico d'Alonzo
12/01/2023, 7:49 PMIntOrList
you provided will be inlines and expanded directly to Int | List<Int>
at all use sites. Compiled code will contain no reference to IntOrList
.
A context receiver is nothing more than an extra parameter of the function that can be used (almost) the same way as a direct receiver, so for our purposes the functions
fun String.direct(): Int = length
context(String)
fun ctx(): Int = length
are the same (albeit called differently where they are used)
both of them will be compiled to extra arguments anyways.
So when you have a function that let's say looks like this fun foo(obj: Int | List<Int>)
you're defining a function that can take as input an Int or a List<Int>.
When you have fun foo(obj: Number & Collection<Char>)
you are defining a function that takes as input a singular object instance that has as a superclass the abstract class Number
and implements the Collection<Char>
interface.
Of course, &
types must still satisfy the constrains imposed by type inheritance. As such the type String & Int
will have no possible instances, same as Nothing
because both String and Int are sealed types which you cannot extend.andylamax
12/01/2023, 8:00 PMFederico d'Alonzo
12/01/2023, 8:01 PMedrd
12/01/2023, 8:01 PMI actually meant intersected types when i typed thatSo it’s not possible…
Int
class is final, for example, so no type will ever be able to implement it.andylamax
12/01/2023, 8:04 PMwith(3) {
with(listOf("a","b")) {
val x = this // x here can be refered to as intersection of Int and List<String>
}
}
But ofcourse, this is very similar to how one would use context receivers which brings me to my whole point aboveandylamax
12/01/2023, 8:05 PMFederico d'Alonzo
12/01/2023, 8:22 PMcontext(Int, List<String>)
fun ctx() {
val a = this // error: 'this' is not defined in this context
val b = this@Int // explicitly reference the first Int type object in the context list
val c = this@List // explicitly reference the first List<*> type object in the context list
size // can call List<String> methods
toLong() // can call Int methods
}
fun (Int | List<String>).type() {
// you don't know if this is either an Int or a List yet, so you can't really call anything with this receiver
println(this::class.simpleName) // there is only one "this", of type Int | List<String>
}
fun main() {
with(3) int@{ // this: Int
with(listOf("a", "b")) list@{ // this: List<String>
val a = this // reference to the closest receiver: List<String>
val b = this@int // explicit reference to the Int receiver
val c = this@list // explicit reference to the List<String> receiver
type() // type called here will *always* output some list class, never an int,
// because it takes the *closest* receiver that fully satisfyes the type
}
}
}
Obviously fun (Int | List<String>).type()
is not real syntaxFederico d'Alonzo
12/01/2023, 8:25 PMcontext(Int, Number)
you get this error, because you would have to always explicitly reference one or the other, which would defeat the purpose of context receivers in the first place.Daniel Pitts
12/01/2023, 9:27 PMDaniel Pitts
12/01/2023, 9:28 PMandylamax
12/02/2023, 12:03 AMandylamax
12/02/2023, 12:04 AMandylamax
12/02/2023, 12:07 AMDaniel Pitts
12/02/2023, 12:47 AMandylamax
12/02/2023, 1:38 AMDaniel Pitts
12/02/2023, 5:34 AMcontext(Outer, TheContext, Nested)
fun Parameter.doThing() = ...
class Outer { fun nested(block: Nested.(Parameter)->Unit) {...} }
...
outer {
nested { parameter ->
with (theContext) {
parameter.doThing()
}
}
}
Daniel Pitts
12/02/2023, 5:35 AMandylamax
12/02/2023, 7:46 AM