Marc Knaup
11/30/2020, 12:33 PMequals()
implementation for lambdas.
Button(
label = "Print count",
onClick = @Equatable { println(count) }
)
data class ButtonProps(val label: String, val onClick: () -> Unit)
That’s useful in Kotlin React development and similar. If the props (arguments) of a component are equal between two rendering attempts then the second rendering can be skipped (memoization). Unfortunately that doesn’t work if the props contain lambdas because they’ll never be equal to the previous instance.
React provides a workaround like val onClick = useCallback(count) { println(count) }
which only uses the passed instance of the lambda if count
has changed and the previous one otherwise. This is more boilerplate and you have to move the lambda further away from where it’s actually used. More importantly it easily leads to errors where you forget to add a dependency (a bound variable) and therefor an old function with different variable bindings will be used.
@Equatable
could tell the compiler to generate an equals
method for that lambda which compares all bound variables automatically - in this case count
. That would solve the issue and eliminate that cause of errors.Yash Grover
12/04/2020, 1:18 PMelse
block too. I once faced a situation where a choice and fallback led to the same action. So I thought this might be something nice to have.
Something like this
fun main():Unit{
var a = 5
when(a){
1,2-> print("Case 1")
5,else -> print("Fallback")
}
}
I don't know if this has been discussed before so I thought I should ask about the suggestion here.samuel
12/15/2020, 2:04 PMclass SomeClass() {
infix fun SomeClass.someFunction (someParam: SomeType) {
// some code
}
}
fun inSomeClass(action: SomeClass.() -> Unit) {
SomeClass().action()
}
inSomeClass {
this someFunction "param"
someFunction "param" // Receiver could possibly implied
}
louiscad
12/15/2020, 10:30 PMwhere
clause that specifies type arguments constraints for a function.
I think that would satisfy many use cases where we currently write multiple functions (often extensions).
The when
expression would also need to be tweaked to make use of this I think, and probably some compiler magic, and maybe @JvmOverloads
, unless these functions are required to have the union type constrained generic argument to be reified
, which requires the function to be inline
, and always inlining the code.Marc Knaup
12/16/2020, 4:24 PMinterface Box<out Value> { val value: Value }
class IntBox(override val value: Int): Box<Int>
val value: IntBox.Value = 1 // IntBox.Value is basically an alias for Int
This would be very useful in generic contexts:
interface Operation<out Result>
interface OperationExecutor<in TOperation : Operation<*>> {
suspend fun execute(operation: TOperation): TOperation.Result // <<< here
}
class IntToStringOperation(val value: Int) : Operation<String>
object IntToStringOperationExecutor : OperationExecutor<IntToStringOperation> {
override suspend fun execute(operation: IntToStringOperation): String =
operation.value.toString()
}
Currently we’d have to introduce a second type argument for that which makes generics more complicated to at the use-site:
interface OperationExecutor<TOperation : Operation<Result>, Result>
object IntToStringOperationExecutor : OperationExecutor<IntToStringOperation, Int>
(same for generic functions)Marc Knaup
12/23/2020, 5:27 PMval Foo.property = "foo"
as syntactic sugar for something like that:
private const val _foo = "foo"
val Foo.property get() = _foo
Typically such a construct is used when the value
• is computed and must not change between invocations
• is expensive to compute even if pure
For example:
// avoid that value changes
val Foo.staticProperty = generateRandomId()
// avoid repeated expensive computation
val Foo.expensiveProperty = someExpensiveSetup()
// avoid temporary object creation
val Foo.someComplexData = SomeComplexData(…)
val Foo.someDependentProp1 get() = someComplexData.prop1
val Foo.someDependentProp2 get() = someComplexData.prop2
val Foo.someDependentProp3 get() = someComplexData.prop3
val Foo.someDependentProp4 get() = someComplexData.prop4
Because such a value is static, i.e. independent of the actual instance of Foo
, you can’t use this
in that context except for referring to other such static values or if the receiver is an object
.
I personally use such properties often in two situations:
• add static values to an object
or companion object
• add static values to marker interfaces (e.g. for DSL)Marc Knaup
12/27/2020, 4:32 PMinterface WithCompanion<T : Any>
fun <T : Any> companionOf(instance: WithCompanion<T>): T =
TODO() // intrinsic
Now the following would be possible:
interface FooInterface: WithCompanion<Any>
fun bar(foo: FooInterface) {
val companion: Any = companionOf(foo)
// …
}
Which would be very useful for a frequent pattern, where you have instances of something that has a shared definition across all instances:
// Types implement `Event` must also have their companion implement `Event.Type`
interface Event : WithCompanion<Event.Type> {
interface Type {
val id: String
}
}
// We can get the companion of an Event instance without reflection.
val Event.type get() = companionOf(this)
// Example
class SunriseEvent : Event {
companion object : Event.Type {
override val id = "sunrise"
}
}
fun storeEvent(event: Event) {
// Not possible like this at the moment without manually adding `type` to every single class implementing `Event`
val typeId = event.type.id
// …
}
Marc Knaup
01/01/2021, 8:10 PMobject
types automatically satisfy equals
and hashCode
requirements of an interface/superclass.
object
types are typically equal to themselves and have a hash code based on their instance automatically which is sufficient.
I can only think of very very few exceptions where you would want an object to not be equal to itself or have some custom implementation 🤔
interface Foo {
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
}
data class Bar1(val bar: Int) : Foo // fine
object Bar2 : Foo // error because equals() and hashCode() are missing
I’m often using explicit abstract equals
and hashCode
to not forget to provide an implementation in implementing types. Typically I use data class
or object
but it’s too easy to forget the data
.Marc Knaup
01/12/2021, 1:23 PMJavier
01/13/2021, 5:21 PMtypealias
for annotations could be shortcuts for annotatinos with params, sample:
typealias ViewModelInject = Inject(param1 = param1, param2 = param2, ...) // doesn't work
I think it is only possible to do
typealias ViewModelInject = Inject
pablisco
01/18/2021, 6:25 PMsealed class Input<A> {
object ForInt : Input<Int>()
object ForString : Input<String>()
object ForDouble : Input<Double>()
}
fun <A> process(input: Input<A>): A = when(input) {
ForInt -> fetchInt() // returns Int
ForString -> fetchString() // returns String
ForDouble -> fetchDouble() // returns Double
}
This won’t compile, despite being correct.
One way to solve it is like this:
@Suppress("UNCHECKED_CAST")
fun <A> process(input: Input<A>): A = when(input) {
ForInt -> fetchInt() // returns Int
ForString -> fetchString() // returns String
ForDouble -> fetchDouble() // returns Double
} as A
The problem with this is that now we get rid of all type safety since we can return any values, which can result on fun runtime errors.
We have all the information there, so I think the compiler should be able to infer the type from the input and the case and tell us if we are returning the right type or not :)
Is there any plan to support auto cast on the result of a when clause like this? If not, I’ll happily create a KEEP for it.Youssef Shoaib [MOD]
01/23/2021, 1:54 PMinline fun <T> identity(block: () -> T): () -> T = block
inline fun <T> perform(block: () -> T): T = block()
fun main() {
println(perform(identity { "hello" })
}
A useless lambda object gets created due to the compiler believing that because the lambda is returned by identity
then it must exist as an actual object. This prevents certain wrapping tricks for lambdas that can provide interesting behaviours without overhead (think for example a lambda being passed into multiple functions one by one that affect whether it executes or not and even does suspend
calls in between and ultimately gets inlined into the call site directly with all that new behaviour being abstracted away from the user). I propose that, whenever you have a lambda being returned by an inline
function, and all of the subsequent usages of that lambda are only passed as inline
parameters, that the lambda gets entirely inlined through all of the inline functions that calls it and ultimately ends up at the call site with all of the wrapping code from the inline
functions around it, just as if you used that exact lambda but rewrote the functions to pass it down instead. Effectively, the above usecase would get compiled down into roughly the equivalent of this:
fun main () {
println("hello")
}
I'd love to know if there's anything currently preventing this from working as expected, because it seems to me that the current inline mechanism should be enough. Yes this will probably mess up debugging possibly, but that could be circumvented just as if you would debug an inline function today.
This is basically just inversing the call stack in a way of how you would do this normally and allows for better decomposition. For example, the usecase above can currently be rewritten to this:
inline fun <T> identity(block: () -> T): T = block()
inline fun <T> perform(block: () -> T): T = identity(block)
fun main() {
println(perform { "hello" })
}
and if you imagine this with actual logic surrounding the lambdas you can basically recreate any example of that form, but it ruins composability and requires the lambda call to always be at the very very bottom of the call hierarchy instead of allowing for more complex lambda-returning functions with no overhead.
For another example, consider this maybe:
val shouldPerform = true
val shouldRunSpecial = true
inline fun <T> specialLogic(block: () -> T): () -> T? = if(shouldRunSpecial) block else { null } // can also be converted to one big lambda instead that does the check on shouldRunSpecial when it's actually called like this: = { if(shouldRunSpecial) block() else null }. Those 2 styles should be roughly equal in the end since all the lambdas get inlined
inline fun <T> performOrThrow(block: () -> T): T = if(shouldPerform) block() else TODO()
fun main() {
println(perform(specialLogic { "hello" })
}
// Turns into this:
fun main() {
println(
if(shouldPerform) {
if (shouldRunSpecial) "hello"
else null
}
else TODO())
}
This is currently what this code runs like logically today, but the only difference is that it'll be all inlined without the need for any lambda objects. Keep in mind that the object itself will need to materialise if it gets used in a non-inline context (i.e. if you pass it to a regular function or query its class for example). This could possibly be extended to fun interfaces themselves being inlined if you use a lambda to call them and then pass them to an inline function, which can have a lot of great impacts for some functional abstractions since it allows you to, for example, have a reference to a fun interface Functor
that gets passed around to only inline functions and in the actual compiled code completely disappears since it isn't needed in any non-inlined contexts. I'm sure the Arrow team and other similar functional proponents would be interested in this because it decreases the cost of those functional abstractions even more (Note: if this can result in some backwards-compatibility issues, then this can instead be a new feature under the name inline fun interface
that works similarly to inline classes but utilises the fact that lambdas that are used in only inline contexts don't ever need to be actual objects and can instead just be copied and inlined everywhere at compile time).
I do realise that this is kind of multiple proposals in one but these 2 features are very connected and kinda go hand in hand because they require similar compile-time machinery, however I personally think that at the very least the first one should be implemented first if it isn't possible to implement both at the moment because then libraries like Arrow can temporarily sacrifice some of the type safety that fun interface
brings in lieu of the optimisation of regular lambdas being inlined when returned by a function. If the whole inline fun interface
idea just sounds too compilcated, then instead we could possibly have `inline class`es with lambdas for their backing field that also receive the same optimisations in this proposal of inlining lambdas along with type safety of course. Personally however I think that inline fun interface
s make more sense because they mirror how inline functions look like. Any thoughts or criticisms about this are greatly appreciated😄!
Again I'd love to know if I made an oversight here and if this could even be impossible, so don't hesitate to point out any flaws in my reasonings.aerb
02/05/2021, 11:19 PMthrowing
?) where the compiler auto-generates interface implementations that just throw
, but that you have an option to override.
This would basically be for creating fakes on massive interfaces without reflection proxying. Eg:
class FakeConnection : java.sql.Connection by throwing {
override fun prepareCall(sql: String) {
// ... some assertion.
}
}
I definitely see potential for abuse, but in a testing context super handy.Nico
02/10/2021, 9:07 PMsealed class Download
data class App(val name: String, val developer: Developer) : Download()
data class Movie(val title: String, val director: Person) : Download()
val download: Download = // ...
// placeholder syntax
val result = when(download) {
is App(.name, .developer: Person(val devName = .name))
if (devName == "Alice") -> "Alice's app $name"
is Movie(.title, .director: Person(val dirName = .name))
if (dirName == "Alice") -> "Alice's movie $name"
is App, Movie -> "Not by Alice"
}
Note how
- Matching is purely nominal thanks to a 'access' operator (I chose .
but could be &
or nothing at all, really)
- Nested patterns are achieved using :
for a 'is-of-type' relation
- Destructured properties (like .name
) are made available in the RHS of the when
clause
- Properties can be desambiguated by explicitly declaring new variables for them
- Guards can be used to match for equality
- Proposed syntax is in the spirit of https://youtrack.jetbrains.com/issue/KT-21661 or https://youtrack.jetbrains.com/issue/KT-44729
If deconstruction in Java is implemented as described in https://github.com/openjdk/amber-docs/blob/master/eg-drafts/deconstruction-patterns-records-and-classes.md#translation, the deconstructed parameters could correspond to the names of those of the record that <deconstruct>()
returnsaerb
02/12/2021, 6:27 PMRegex("\\s\\\\")
becomes r"\s\\"
?stantronic
02/23/2021, 12:23 PMprivate typealias Binding = ItemRotaBinding
in the IDE without lint errors, but then when I try to create another one (e.g. private typealias Binding = ItemShiftBinding
) in an adjacent file, it complains about redeclarationRuckus
02/23/2021, 6:21 PMget
and set
operators
Currently, you can have default parameters in get
and set
operators, but you cannot address them out of order. For example, given this:
class Thing<T>(...) {
operator fun set(coordinates: IntArray, depth: Int = this.depth, isDestructive: Boolean = true, value: T) { ... }
}
Which allows this:
thing[coordinates] = value
But it would be nice to be able to do this:
thing[coordinates, isDestructive = false] = value
However, there doesn't appear to be a way to do so. Instead, I'm stuck either adding a second set
operator, or doing
thing[coordinates, thing.depth, false] = value
(Which only works assuming thing.depth
is accessible. If not, and there isn't an overloaded set
operator, I don't believe it's possible.)
Yes, I am aware this can be simply worked around by calling the set function directly and not using the operator, which is why this is a proposal and not a bug report.Zach Klippenstein (he/him) [MOD]
02/23/2021, 9:20 PMKevin K
02/25/2021, 4:30 AMmeasureTimedValue
method to measure time while still allowing capture of the code block result. I found it a little cumbersome to work with the TimedValue
result though; it isn't very fluent. I made this extension function to help, and I was thinking maybe it would be a good addition to the API?
@ExperimentalTime
inline fun <T> TimedValue<T>.capture(block: (Duration) -> Unit): T {
block(duration)
return value
}
@ExperimentalTime
fun main() {
// Without the capture function
val (result, duration) = measureTimedValue {
Thread.sleep(500)
"foobar"
}
println("Duration: $duration")
println("Result: $result")
// With the capture function
measureTimedValue {
Thread.sleep(500)
"foobar"
}.capture {
println("Duration: $it")
}.also {
println("Result: $it")
}
}
(P.S. - I'm not very satisfied with the name capture
...feel like there's definitely a better word to use, just can't think of it)deviant
03/12/2021, 2:20 PMmelatonina
03/15/2021, 9:07 AMfun f(a: A, b: B): T = TODO()
fun g(a: A?, b: B?) {
val x = f(a?, b?)
}
where x
has type T?
and is null
if either a or b or both are null
, instead of
fun g(a: A?, b: B?) {
val x = a?.let { actualA -> b?.let { actualB -> f(actualA, actualB) } }
}
or
fun g(a: A?, b: B?) {
val x = if (a != null && b != null) f(a, b) else null
}
which would not work if a
or b
were var
, or
fun g(a: A?, b: B?) {
val x = a?.let { b?.let { f(a, b) } }
}
which is a bit silly and would not work if a
or b
were var
.
Not groundbreaking but I often would like it.Ben Woodworth
03/29/2021, 5:29 PMinterface SomeTrait { ... }
private class SomeTraitImpl(...) : SomeTrait { ... }
fun SomeTrait(...): SomeTrait = SomeTraitImpl(...)
And used like this:
class MyClass : SomeTrait by SomeTrait(...)
// Or just used to get an instance of the default implementation:
val trait = SomeTrait(...)
What if a default implementation could be given, something like this:
interface SomeTrait {
...
default class(...) { // Optional name, like companion objects. Implicitly implements SomeTrait.
...
}
}
// No need for a pseudo-constructor function:
val trait = SomeTrait(...) // gets an instance of the default class
Maybe treat "extending" this interface as delegation to the default implementation:
class MyClass : SomeTrait(...)
// Desugars to:
class MyClass : SomeTrait by SomeTrait(...)
It'd probably just be unnecessary added complexity to the language, but it seems like it has potential to simplify some things, it avoids the whole *Impl
naming controversy, and it's definitely fun to consider :)hfhbd
04/10/2021, 9:36 AM2.days or Duration.days(2)
I would bring a pitch solving this design:
val timeToFix: Duration = .days(2)
This design is used in Swift. In Swift, every static function (or enum case) can be called by using a short dot annotation. This is also useful in function parameters
fun something(r: Result<String>) { }
something(.success("Hello")
In Kotlin, there are no static functions, but there is a companion object
. All functions of the companion object could be called using this syntax.Fleshgrinder
04/10/2021, 4:46 PM\n
vs \r\n
). Basically what we should have:
println("""\
This is the first line in this \
multi-line string.
This is the second line in this \
multi-line string.
""")
The above would result in:
This is the first line in this multi-line string.
This is the second line in this multi-line string.
I guess the fact that the backslash has no special meaning in multi-line strings today would be a blocker for this, because it suddenly would need to be escaped and existing code would break. At the same time it would have been nice to use it as escaper so that we could simply write \$
instead of ${'$'}
but too late. I fear an alternative approach is needed here. Maybe f""
for a multi-line string with normal escape behavior (so \"
needs to be used). 🤔 Anyways, just wanted to get this off my chest.eduardog3000
04/16/2021, 2:37 PMexampleString.toInt() !: -1
Christian Dräger
04/22/2021, 7:09 PMI am writing a DSL that allows to parse HTML in kotlin.
(example: <https://github.com/skrapeit/skrape.it#parse-and-verify-html-from-string>)
since * is common to use in this context (e.g. when it comes to css selectors) it would feel natural if the DSL could do as well. Therefore it would be handy if it would be possible to overload a unaryTimes operator.
since kotlin already supports unaryPlus and unaryMinus overload, why there isn't a unaryTimes?
here is an example what it looks like right now:
@Test
fun `can parse html from String`() {
htmlDocument("""
<html>
<body>
<h1>welcome</h1>
<div>
<p>first p-element</p>
<p class="foo">some p-element</p>
<p class="foo">last p-element</p>
</div>
</body>
</html>""") {
h1 {
findFirst {
text toBe "welcome"
}
p {
withClass = "foo"
findSecond {
text toBe "some p-element"
className toBe "foo"
}
}
p {
findAll {
text toContain "p-element"
}
findLast {
text toBe "last p-element"
}
}
}
}
}
i think this should be possible right now by making * an infix operator?
@Test
fun `can parse html from String`() {
htmlDocument("""
<html>
<body>
<h1>welcome</h1>
<div>
<p>first p-element</p>
<p class="foo">some p-element</p>
<p class="foo">last p-element</p>
</div>
</body>
</html>""") {
h1 {
find 0 {
text toBe "welcome"
}
p {
withClass = "foo"
find 1 {
text toBe "some p-element"
className toBe "foo"
}
}
p {
find * {
text toContain "p-element"
}
findLast {
text toBe "last p-element"
}
}
}
}
}
what would be possible due to the proposal of having unaryTimes operator to overload:
@Test
fun `can parse html from String`() {
htmlDocument("""
<html>
<body>
<h1>welcome</h1>
<div>
<p>first p-element</p>
<p class="foo">some p-element</p>
<p class="foo">last p-element</p>
</div>
</body>
</html>""") {
h1 {
0 { // already possible by invoke operator
text toBe "welcome"
}
p {
withClass = "foo"
1 {
text toBe "some p-element"
className toBe "foo"
}
}
p {
* { // would need unaryTimes
text toContain "p-element"
}
findLast {
text toBe "last p-element"
}
}
}
}
}
spand
04/23/2021, 7:54 AMT?
. Here I invented the T!!
type operator for it. Should be clear from the example here why it could be useful.
interface LoadingCache<K : Any, V> {
fun get(key: K): V // <-- If it can return null then it must be specified by type V
// If load returns null then entries are missing hence it doesnt make sense to have here : Map<K,V?>
fun getAll(keys: Iterable<K>): Map<K, V!!> {
return keys.mapNotNull { k -> get(k)?.let { k to it } }.toMap()
}
}
Thoughts ?mikehearn
05/12/2021, 2:53 PMMarc Knaup
06/03/2021, 6:16 PMabstract
functions and properties. If an unimplemented one is called it throws a NotImplementedError
.
Ex. unit test for `FooCreator`:
interface FooRepository {
fun add(foo: Foo)
fun get(id: String): Foo?
fun delete(id: String)
}
var actual: Foo? = null
val creator = FooCreator(repository = test object : FooRepository {
override fun add(foo: Foo) {
assertNull(actual)
actual = foo
}
})
creator.create(id = "bar")
assertEquals(actual, Foo(id = "bar"))
Alternatively full-fledged mocking of abstract funs/props and setters to set/unset each implementation.ushort
06/19/2021, 3:19 PMif (val foo = someClass.someProperty == 1) {
// can access foo here
}
ushort
06/19/2021, 3:19 PMif (val foo = someClass.someProperty == 1) {
// can access foo here
}
dmitriy.novozhilov
06/19/2021, 3:37 PMval foo = someClass.someProperty == 1
reads as val foo = (someClass.someProperty == 1)
, not (val foo = someClass.someProperty) == 1
(like val
declaration in when
)
If you really don't want to intoduce foo
to outer scope you can use some scope function
someClass.someProperty.let { foo ->
if (foo == 1) { ... }
}
or just replace if
with when
when (val foo = someClass.someProperty) {
1 -> ...
}
ushort
06/19/2021, 3:40 PM