Gat Tag
11/18/2024, 6:05 PMAlejandro Serrano.Mena
11/20/2024, 11:11 AMall
meta-target
The discussion takes place in https://github.com/Kotlin/KEEP/issues/402, any feedback is more than welcome 🙂Youssef Shoaib [MOD]
12/06/2024, 7:24 PMDaniel Pitts
12/14/2024, 2:44 AMEdgar Avuzi
12/24/2024, 2:49 AMfor comprehension
in KotlinFudge
01/02/2025, 10:16 AMeygraber
01/02/2025, 7:15 PMJP Sugarbroad
01/02/2025, 9:29 PMconst
is limited to strings & numbers, but Java 11+ supports dynamic constants. Any thoughts on whether it's worth adding them?Daniel Pitts
01/11/2025, 1:08 AMEdoardo Luppi
01/16/2025, 10:08 PMExerosis
01/23/2025, 8:54 AMCLOVIS
02/03/2025, 7:03 PMinterface Value<T>
infix fun <T> Value<T>.eq(other: Value<T>): Value<Boolean>
So far, this is quite simple and well-solved by Kotlin. However, there are types which can be seen as Value<T>
which don't implement the interface, because I don't own them. In this particular case, there are 4:
• instances of the interface Value<T>
, for example as returned by other operators
• instances of T
(the regular Kotlin type)
• instances of KProperty1<*, T>
• instances of Field<T>
(another type, describing what it is is not relevant to this discussion)
Solution 1. Overloads
Therefore, to typesafely accept all of these values, I would have to declare all operators 8 times (since they accept two operands which can each have 4 different types). Since there are more than 30–40 operators, that would result in a lot of declared extension functions, which is probably not great for compilation performance.
someValue eq true
Solution 2. Conversion method
An alternative solution that is currently possible is to have the user call a conversion method to convert non-Value<T>
types. For example, it could be called of
.
someValue eq of(true)
This is, by far, the simplest solution at the moment. However, it implies a burden on the user, and calling of
everywhere can make usage quite verbose:
of(User::score) set cond { of(User::role) eq of(User::candidate) }
.then { of(1) }
.else { of(User::score) add of(1) }
I think you'll agree that this is quite a bit harder to read than
User::score set cond { User::role eq User::candidate }
.then { 1 }
.else { User::score add 1 }
Because it burdens the user, I don't think this solution is what library authors should use at the moment. However, it can be a part of a larger solution. For example, having these conversion functions makes the overload solution trivial to implement, as all overloads just delegate to the main one.
Proposed solution 3. Proper union types
Based on the way the problem is described, the operand types could be declared as
union Operand<T> = Value<T> | T | KProperty1<*, T> | Field<T>
There already exists a lot of discussion on union types, so I won't detail this solution further.
Proposed solution 4. Fake union types
This idea is similar to union types for callers, but it doesn't require union types to exist at runtime. The idea is to coalesce all cases into a single regular type through a conversion function, and Kotlin compiler just has to call the provided conversion functions. In this example, the canonical type is Value<T>
, so we could imagine a new operator that converts from other type.
interface Value<T> {
// …
operator fun from(other: T) = …
operator fun from(other: KProperty1<*, T>) = …
operator fun from(other: Field<T>) = …
}
In a way, this is very similar to C++'s implicit conversion methods, but declared in reverse: instead of a type describing what it can be converted into, a type described what it can be converted from. I believe this is much safer and less surprising, but it may still be much too surprising. This could also be very badly abused if the conversion methods aren't pure (in an FP sense).
Proposed solution 5. Typeclasses
I know that other languages have used typeclasses to solve this issue. To be honest, I'm not fully familiar with how they work, other than "typeclasses are interfaces that you can implement remotely". Since typeclasses have been proposed to Kotlin in the past without success, I assume situations similar as this were already discussed and this message won't bring them back on the table.
Also, I have heard multiple people mention context parameters could help emulate them, so maybe there is an additional context parameter solution here? But I'm not seeing it.
That's about all the options I thought of. Are there other existing ways to solve this use-case? If not, are there other potential solutions being discussed? I'm curious how this could move forward.Youssef Shoaib [MOD]
02/05/2025, 5:38 PMComparable<*>
as an inferred type.Fudge
02/14/2025, 10:19 AMYoussef Shoaib [MOD]
03/14/2025, 2:33 AMSelectBuilder
, Raise
). Scala's presentation of it is quite technical, so I wonder if it could be simplified to be more developer-friendly. I'm also writing an effects library where that would come in handy to prevent effects escaping their scope.Youssef Shoaib [MOD]
03/16/2025, 12:23 AMvar x = null
x = 5
So that x gets inferred as Int?
. I think this was meant to work like builder inference by having a postponed type variable for the type of x, so that the type variable can be inferred as such.I believe this is very similar to OCaml's weak polymorphic types. Anyone remember where that idea was presented/proposed?Youssef Shoaib [MOD]
03/19/2025, 10:33 AMgetValue
and setValue
were practically useless (since they're the same ones you can capture in provideDelegate
). Is there a world where `getValue`'s contexts correspond to `get`'s contexts, and analogously for set
?alexhelder
03/21/2025, 12:09 AMclass Facade(
private val delegate:Delegate
) {
fun aaa(a,b,c,d) by delegate::bbb
}
class Delegate {
fun bbb(a,b,c,d) { ... }
}
If the number of arguments or functions increases, doing it manually like fun aaa(a,b,c,d,e,f) = delegate.bbb(a,b,c,d,e,f)
becomes tedious. This seems useful if a class needs to delegate to another class, only a subset of some interface, or does not want to implement the whole interface via class delegation.CLOVIS
03/21/2025, 8:24 PMDaniel Pitts
03/30/2025, 3:35 AMansman
04/01/2025, 4:13 PMtitle: context(Component.Slot<Text>, Component.Slot<Icon>) RowScope.() -> Unit
fun Component.Slot<Text>.Text(...) {}
In Kotlin 2.1.10 this worked fine:
title = {
Title()
}
But in 2.1.20 this is an error with:
cannot be called in this context with an implicit receiver. Use an explicit receiver if necessary.
The issue is that you don't seem to be able to actually add a context receiver as there doesn't seem to be a way to access the context receiver.Raymond
04/04/2025, 12:31 AMreadonly
-like keyword for class properties in Kotlin.
For example, something like this:
class ComponentDTO(
val primaryText: String,
val secondaryText: String? = null,
readonly val category: Category = Category.Informative,
readonly val drop: Drop = Drop.Down
)
Which would internally be equivalent to:
class ComponentDTO(
val primaryText: String,
val secondaryText: String? = null,
) {
val category: Category = Category.Informative
val drop: Drop = Drop.Down
}
I think this could be particularly useful, especially if it can be implemented without breaking binary compatibility when removing the readonly
keyword.
Would love to hear your thoughts or if there's something similar already being considered!Stephan Schröder
04/04/2025, 1:27 PMtailrec
is probably one of least used Kotlin features, and this is me wondering if it couldn't be improved with some additional syntactic sugar (not that it'd improve its usage much).
TLDR: can we make the accumulator parameter invisible from the outside!?
So let's start with a factorial
-function using tail recursion:
tailrec fun factorial(n: Int, f:Long): Long {
if(n<2) return f
return factorial(n-1, n*f)
}
my problem is the second parameter, the accumulator. For factorial
is only valid parameter for it is 1
. But I don't want to hardcode 1
at every invocation and/or even give a programmer a chance to provide the wrong value. So let's use an optional parameter:
tailrec fun factorial(n: Int, f:Long = 1): Long {
if(n<2) return f
return factorial(n-1, n*f)
}
at least now I don't have to provide the initial value anymore, i can write val x = factorial(6)
, but unfortuanately I can still provide a wrong parameter. An IDE will still suggest, that I provide one. So if I want to avoid that, I have to wrap my tailrec-fun inside a normal function.
fun factorial(n: Int): Long {
tailrec fun _factorial(n: Int, f:Long): Long {
if(n<2) return f
return _factorial(n-1, n*f)
}
return _factorial(n, 1)
}
now the using factorial
is completely safe, but there is boilerplatecode involved and invocation overhead.
So how about Kotlin gains a new keyword (let's call it) acc
(for accumulator) where you have to provide an initialisation value and you can't set the parameter from outside the function itself!
tailrec fun factorial(n: Int, acc f:Long = 1): Long {
if(n<2) return f
return factorial(n-1, n*f)
}
now we're back to boilercode-free code that can only be invoked savely from the outside with one parameter.
Alternative to naming it acc
: a new keyword maybe to much to ask for, so how about using private
. A private parameter (of a non-private function) has to have a default value which can only be supplied from within a private invocation context, so recursion would work 🤷♂️
tailrec fun factorial(n: Int, private f:Long = 1): Long {
if(n<2) return f
return factorial(n-1, n*f)
}
question 1: if private
parameters are allowed, what about internal
parameters?
question 2: Independently which name is used for the keyword, a remaining question is if this keyword should only be usabe within tailrec functions 🤔 (My initial thoughts are that I'd prefer acc
over private
if it's only allowed in tailrec fuctions)Klitos Kyriacou
04/08/2025, 4:27 PM<
, <=
>=
and >
which all call compareTo
but they all check for inequalities. Would there be any interest in a special operator, <==>
, with the semantics that a <==> b
is equivalent to a.compareTo(b) == 0
? Let's call it the "extended spaceship operator" since the normal spaceship operator <=>
is already well-known in other languages and has different semantics (it returns an integer). The use case is when comparing BigDecimals
for value equality (where a == b
is not what is wanted because it includes scale).ansman
04/21/2025, 11:59 PMfun foo(content: context(String, String) () -> Unit) {
content("", "")
}
fun test() {
foo {
// Does not work: Multiple potential context arguments for 'String' in scope.
implicit<String>()
}
}
Daniel Pitts
04/27/2025, 6:50 PMpackage foo.bar
data object Constants {
val first = 1
val second = 2
}
Main.kt
import foo.bar.Constants.*
fun main() {
println("$first is first. $second is second.")
}
Kelvin Chung
04/29/2025, 7:31 PMArjan van Wieringen
05/07/2025, 5:28 PMJP Sugarbroad
05/07/2025, 6:14 PMasync let
and I really want it: https://blog.yoshuawuyts.com/automatic-interleaving-of-high-level-concurrent-operations/phldavies
05/12/2025, 10:26 AMcontext-parameters
(2.2.0-Beta2) is it expected to be ambiguous in the call to helloContext
when nested inside two applicable scopes? I would have expected it to resolve the same way as the receiver case (that is, with the closest applicable candidate taking priority, given you can disambiguate to the outer scope explicitly with a with(this@outer)
or context(this@outer)
)
(snippet in thread - slack went weird and thought it was a binary)