```private fun firePositionChangeEvent() { val...
# language-proposals
l
Copy code
private fun firePositionChangeEvent() {
    val x = x
    val y = y

    if (x != null && y != null) {
        tile = onPositionChange(x, y)
    }
}
// becomes
private fun firePositionChangeEvent() {
    tile = if (catch val x != null && catch val y != null) onPositionChange(x, y) else null
}
Created KT-65359, proposing adding
catch val
, a way to "catch" the current value of a mutable/delegated property for smartcasting purposes.
l
@jw so the if checks if
nullable
isn't
null
? Nope, it's different...
j
Swift binding let's you put a
let
to capture the subject in a local, like we can already do with a
val
in a
when
l
basically, allow
val foo = etc
to be used everywhere?
j
It's also used to do lightweight optional checks unless you include other conditionals
l
Don't want it to do any checks, you're modifying the value that way
j
The issue Jake shared is similarly solving this same problem, just with a different syntax based on a Swift language construct. The main difference is being able to assign the captured variable a different name, rather than shadowing the name in your example. But in practice, in my experience it's common to shadow the name when doing this in Swift, e.g.:
Copy code
if let x = x, let y = y {
    tile = onPositionChange(x, y)
} else {
    tile = null
}
In Kotlin this might be:
Copy code
tile = if (val x = x && val y = y) {
   onPositionChange(x, y)
} else {
    null
}
The variable assignment evaluates to true if not null. And in Swift the
,
is equivalent to
&&
logical operator. This would be equivalent to your example:
Copy code
tile = if (catch val x != null && catch val y != null) {
    onPositionChange(x, y)
} else {
    null
}
(You have the
null
and
onPositionChange
swapped in your code.)
I'm not sure
catch
is a good keyword for this usage, since it already has a purpose in catching exceptions.
capture
would be more indicative of the behavior, but that's a longer syntax than the Swift construct proposal.
l
@Jeff Lockhart
(You have the
null
and
onPositionChange
swapped in your code.)
Oh, didn't notice, thanks!
I'm not sure
catch
is a good keyword for this usage, since it already has a purpose in catching exceptions.
Fair. It was an example keyword. I would like to link Java's keyword creation guidelines (de juro, it's a draft, but de facto, it's already used). I think the existing keyword is a nice match, and I don't think it conflicts too much - in Kotlin, `catch`es always show with curly braces around them (
} catch {
). But it's a question of what's more important for Kotlin, backwards compatibility, or non-confusing keywords.
val x = x
Yeah, I really don't want to see that in Kotlin code. It's just boilerplate-y. The only place where
val x = x
is better than
catch val x
is top-level mutable properties, because they cannot be accessed via an explicit receiver -- that is, once you "catch" them, it's forever, you cannot access the original property anymore. However, my reply to this is: Don't use mutable top-level properties. You could introduce a way to de-declare a local property tho ---
drop x
?
j
Would there be another usage for
catch val x
that doesn't do a
!= null
check? The Swift construct does the logical null check automatically, making it a more concise syntax. I would expect both syntaxes' variables are only valid for the scope of the if block. This is consistent with Kotlin (and other languages) variable scoping.
Swift also has a
guard
language feature, which allows capturing a non-null variable for the scope of the function following the
guard
. E.g.:
Copy code
guard let x = x, let y = y else {
    tile = null
    return
}

tile = onPositionChange(x, y)
This would be similar to Kotlin:
Copy code
val x = x ?: run {
    tile = null
    return
}
val y = y ?: run {
    tile = null
    return
}
tile = onPositionChange(x, y)
where you have to check variables individually, similar to the if capture non-null checks, which can be chained in Swift, but isn't possible now with Kotlin's
let
, which I assume is a similar goal to your proposal.
r
> Would there be another usage for
catch val x
that doesn't do a
!= null
check? > Perhaps for type refinement
if (catch val x is Foo) ...
l
@Jeff Lockhart yes,
is
expressions, expensive property computation, etc. Note this syntax isn't exclusive to ifs - but to any place where a property is used (even a simple
catch val x
, without using the variable), so limiting it to the boundaries of the if might be confusing. Swift's
guard
can be implemented via a Kotlin stdlib function -- it is basically a fancy if:
Copy code
if (catch val x == null || catch val y == null) {
    tile = null
    return
}

tile = onPositionChange(x, y)
This is a very Java way to do this, I don't think it fits Kotlin.
👍 1
w
Cool that you're contributing and thinking about language design! One of the most important things are the use cases you present. The main use case you present is relatively easy to solve with the current feature set:
Copy code
tile = x?.let { x -> y?.let { y -> onPositionChange(x, y) } }
You might want to focus on use cases where this is not possible.
m
indeed, we made an extension function for nullables like so:
Copy code
inline fun <R, A, B> withNoNulls(p1: A?, p2: B?, function: (p1: A, p2: B) -> R): R? =
    p1?.let { p2?.let { function.invoke(p1, p2) } }
which becomes
Copy code
withNoNulls(x,y){ x, y -> onPositionChange(x,y) }
or better yet
Copy code
withNoNulls(x,y,::onPositionChange)
1
l
@Wout Werkman tbf, that syntax is absolutely horrendous. One of the main usecases could be extension, virtual/`open`/`abstract`,
field
-less, and delegated properties. Beyond smartcasting, those properties are evaluated every time they're used. Developers unaware of this might not use
val x = x
, or might even decide the code ugliness is worth the performance hit (which is much common than one would think it is, I've done this myself a lot more than I would like to admit). By having
catch val
, one could implement warnings for using said properties a lot or in
suspend
functions (potentially together with
@NonExpansiveThreadSafeProperty
and
@ExpansiveThreadUnsafeProperty
stdlib annotations). Even worse than a performance hit, this can cause thread safety issues (as we've mentioned before) - using
catch val
for all properties as a good-code-standard (unless, of course, you need the actual, current value) could mitigate those from appearing in the first place.