Steven Shaw
06/13/2023, 11:07 PMwhen
..is
). AFAICS, pattern match is superior in Java. Am I missing something? Are improvements on the roadmap?ephemient
06/13/2023, 11:12 PMswitch
pattern matching have become more expressive than the equivalents in Kotlinephemient
06/13/2023, 11:13 PMStephan Schröder
06/14/2023, 8:15 AMStephan Schröder
06/14/2023, 8:24 AMwhen(val outer: Outer = ... ) {
is Outer.Inner1 -> {println(outer.str)}
is Outer.Inner2 -> {println(<http://outer.nr|outer.nr>)}
}
the complete example is hereephemient
06/14/2023, 8:28 AMswitch (any) {
case Pair(var first, var second) -> // ...
}
takes the form of
when (any) {
is Pair -> {
val (first, second) = any
// ...
ephemient
06/14/2023, 8:29 AMwith(pair) {
first + second // can use directly
but it's not really the same as destructuring, it's just bringing those properties into scopeephemient
06/14/2023, 8:33 AMwhen
in Java)… you can see a request in https://youtrack.jetbrains.com/issue/KT-13626/Guard-conditions-in-when-with-subject which is a child of one of the previous linksCharles Flynn
06/14/2023, 3:44 PMephemient
06/14/2023, 5:04 PMSteven Shaw
06/14/2023, 11:57 PMcomponentN
functions!
@Stephan Schröder, I saw the when ... is
, but that is what is disappointing compared with Java.
@ephemient, the scoped functions look interesting as a workaround to
when (any) {
is Pair -> {
val (first, second) = any
which makes me sadSteven Shaw
06/15/2023, 12:04 AMSteven Shaw
06/15/2023, 12:06 AMephemient
06/15/2023, 12:31 AMSteven Shaw
06/15/2023, 3:55 AMStephan Schröder
06/16/2023, 10:37 AMSteven Shaw
06/18/2023, 3:38 AMSteven Shaw
06/18/2023, 3:49 AMStephan Schröder
06/18/2023, 10:03 AMSteven Shaw
06/20/2023, 1:40 AMSteven Shaw
06/20/2023, 1:47 AMSteven Shaw
06/20/2023, 1:50 AMStephan Schröder
06/20/2023, 9:34 PMfun main() {
println(A.s.length)
}
class A {
companion object {
val s: String = B.s
}
}
class B {
companion object {
val s: String = A.s
}
}
playground: https://pl.kotl.in/RwDuuVq41
b)
fun main() {
B()
}
abstract class A {
constructor() {
print()
}
abstract fun print()
}
class B:A() {
val s: String = "abc"
override fun print() {
println(s.length)
}
}
you can check it here: https://pl.kotl.in/ujk8CcpOw
c) I forgot c 😅
So you can produce NPEs. I'm pretty sure b) is the most likely candidate. But It never happened to anyone I know in the 5 years I've been using Kotlin professionally 🤷♂️
I think the kind of NPEs you can trigger in Kotlin are very likely to be triggered every single time, not randomly depending on incoming data (though I'm sure you can construct those case).JasonB
06/20/2023, 9:38 PMnull
is a natural concept, we just have to be deliberate about handling itJasonB
06/20/2023, 9:38 PMnull
is not knowing if something is null
or notSteven Shaw
06/21/2023, 2:08 AMSteven Shaw
06/21/2023, 2:11 AMfind
.Steven Shaw
06/21/2023, 2:15 AMSteven Shaw
06/21/2023, 2:21 AMfind
.Stephan Schröder
06/21/2023, 6:48 AMprintln(xs.find { it == null }) // Is the null found or not? -> yes it's null because it returns the object what matches the predicate, and the object is clearly null
find
is the wrong function, it returns the object, not the boolean if it's in there. nulls are not meant to be used as a form of boolean values. What you're looking for is any
val xs = listOf(3, null)
println(xs.any { it == 3 }) -> true
println(xs.any { it == 4 }) -> false
println(xs.any { it == null }) -> true
and this will of course return true
A nullable type is excatly ok, if data can be absent in the domain model your code is representing. Typesystems with nullable types allow you to get rid of the bad source of nulls (invalid data) but retain the good source of nulls (possible absence of something within the domain).Steven Shaw
06/22/2023, 4:14 AMThe problem with typesystems without nullability isn't that null exists, but that you don't know which values can't contain null and therefore everything is implicitly nullable.However, imagine if you don't have
null
then you don't need a special type system that tracks it with special operators that handle it. This doesn't mean that I don't appreciate Kotlin. Since Java has nulls, then it's good to track them in Kotlin.
I quibble with the following:
nulls ... denote the absence of things.People do use
null
to indicate absence, but they need not. Ideally, we can use Option for that and not be penalised at runtime. To me, it seems strange to model two choices with a nullable Address (i.e. present, not present) but a different mechanism when there are more than 2 choices. Preferring "sum types" for each of these cases keeps the language smaller. Another idea, that Scala 3 and TypeScript use, is to handle nullability using union types which also has an economy of expression. I'm unsure if union types are necessary.
Switching tois the wrong functionfind
any
works only in the small, not in the large. It comes back to the fact that it isn't possible to distinguish the return value of find
from a null
that was found from a null
meaning "not found". It's a case where there are three choices but the API limits to just 2. This could be fixed by using Option.
To see that switching to any
isn't sufficient, imagine that we want to do something with the item if it was found, not just knowing that it is present. Then I wouldn't know if I had found the null
or not.Steven Shaw
06/22/2023, 4:15 AMephemient
06/22/2023, 4:24 AMnull
references requires a different initialization model, which you can see if you read the previous links or compare to SwiftSteven Shaw
06/22/2023, 4:29 AMJasonB
06/22/2023, 4:39 AMStephan Schröder
06/22/2023, 7:56 AMnull
to indicate absence, but they need not. Ideally, we can use Option
@Steven Shaw I don't see much difference between Option
and a nullable type. If you have a language with Option your syntax for null
is basically Option::None
🤷♂️
If you force me to pick a prefered method, I'd pick a typesystem with nullable types anyway, because I can simply assign a non-nullable value to a nullabple one, but I can't assign a value to an option, I have to explicitly wrap it.
I do use Rust and therefor Option. so I have experience and do like both approaches (because both give me the same thing, they force me to handle the possible of absence of data).
I guess people with a functional background would prefer Option, since Option is a Monad ("look at all the things we can achieve with this one concept") and nullable types are probably way more heavy-weight, alas, I don't have a functional background and I don't have to implement a nullable typesystem either, I can simply use one 🤷♂️Stephan Schröder
06/22/2023, 9:21 AMfind
is simply the wrong method to use here, not that there's something wrong with nullable types.
Not sure what you mean with "'any' doesn't scale", both methods have the same runtime (O(n)), which means both are equally inefficient.
So let me fix your method for you
fun <T> Iterable<T>.findAndPrint(x: T) {
if (this.any{it==x) {
println(x)
}
}
additionally most of the time I filter out nullable values as soon as possible, so at least 95% (made up number but feels correct) of my collections contain a non-nullable type, so you could actually even use find
on those.
fun <T> Iterable<T & Any>.findAndPrint(x: T) { // it doesn't make that much sense to look for a nullable value in a non-nullable collection. I just wanted to stay close to your code
val item = this.find { it == x }
if (item != null) {
// We found it! or did we? Yes, yes, we did!
println(item)
}
}
of course the next step would be to shorten this method Kotlin-style 😎
fun <T> Iterable<T & Any>.findAndPrint(x: T) {
this.find { it == x }?.let{println(it)} // yes, i know, sometimes I do use null as a boolean, but only in a non-nullable context (<T & Any>)
}
Since you're new to Kotlin let me point you into the direction of filterNotNull.
I love how it not only removes all the nulls from an Iterable but also retains this information in the fact that its return type is now an Iterable of an non-nullable type.
And of course you could do the same with Option, my point isn't that nullable types are better, but that both approaches are functionally equivalent.
I'm just confused that you seem to think that Option is better somehow.JasonB
06/22/2023, 5:05 PMSteven Shaw
06/22/2023, 9:34 PMJasonB
06/22/2023, 9:38 PMSteven Shaw
06/22/2023, 10:25 PMSteven Shaw
06/23/2023, 12:27 AMSteven Shaw
06/23/2023, 12:45 AMfilterNotNull
(catMaybes
to me). I do like the conciseness of idiomatic Kotlin (with the scope function). I haven't seen the & Any
syntax and so don't know that that is. This will make a good study for me. In a way, I was kind of surprised that my code compiled (when using T
instead of T?
). I imagine that a type parameter can take on any type (including a nullable one), but it is a bit confusing. My function findEm
operates on a generic container — I think subtyping must allow the use of integers and null there 🤷♂️.
Note that I didn't say that "any" doesn't scale, but that "switching to any" doesn't work in the large. What I mean is that you won't always be able to find workarounds when using nullable types instead of Option (at least not without duplicating code). I'll see if I can come up with another small example which shows this more clearly. It probably boils down to being able to have Option<Option<String>>
but String??
— which isn't illegal as I suspected — is just synonymous with String?
. If Kotlin allowed nullable of nullable of String, it couldn't be represented as a nullable reference in the runtime (because there's another case to consider).Steven Shaw
06/23/2023, 1:00 AMfindAndPrint
use a predicate instead of a value to compare? I don't think we can use any
then as it doesn't return the value found. Here's the example. I haven't looked up about the & Any
thing yet!
fun main() {
fun <T> Iterable<T>.findAndPrint(predicate: (T) -> Boolean) {
val item = this.find(predicate)
if (item != null) {
// Are we sure that we found it?
println("item = $item")
}
}
fun <T> findEm(xs: Iterable<T>) {
xs.findAndPrint { it == 3 }
xs.findAndPrint { it == 4 }
xs.findAndPrint { it == null }
}
val xs: Iterable<Int?> = listOf(1, 2, 3, null, 5, 6, 7)
println("xs")
findEm(xs)
val ys: Iterable<Int> = listOf(1, 2, 3, 4, 5, 6, 7)
println("ys")
findEm(ys)
}
https://pl.kotl.in/5H8puOH86Steven Shaw
06/23/2023, 1:02 AMany
, there is the problem that the null cannot be found.Steven Shaw
06/23/2023, 1:02 AMSteven Shaw
06/23/2023, 1:03 AMStephan Schröder
06/23/2023, 8:36 AMany
, there is the problem that the null cannot be found.
well, technically the null is found, it's just not discernable if a null is due to a null being found or the null value not being present.
ok, so if we'd be using Option here, we'd be getting an Option.of(Option.None) and it would still be discernable. got it.
So when working on a collection of an optional/nullable type (something like) find
is a valid choice in a language with Option, and a flawed choice for a language with a nullable typesystem.
Luckily, there or other choices/methods that are not even more cumbersome to use to work around this "problem" 🤷♂️Stephan Schröder
06/23/2023, 11:10 AMfind
in Kotlin, as long as the predicate you provide doesn't match on null
. While I do understand that in a mathematical sense I can construct a predicate that also matches on null
for every predicate that doesn't, in a practical sense I don't remember . Of course academic purity is its own reward.
null
is basically an Option::None
with an automatic flattening, where any amount of nested Option::Some(Option::Some(...(Option::None)))
collapses to Option::None
.
So on the on hand you loose structural information, on the other hand you also loose a lot of verbosity.
"eradicate null from the language" sounds so powerful, it's a bit disappointing once you realize the difference is not that big
or in other words:
In a language with a nullable typesystem null
is extremely close to Option::None
.
Our options are not that far from each others either 😅 you even agreed that give Kotlin's interaction with Java a nullable typesystem is preferable. I think we might have slipped a bit into an uncanny valley of sorts 😅Steven Shaw
07/07/2023, 12:35 AMYes, that's the crux of it. However, Kotlin does have Option, so theis basically annull
with an automatic flattening, where any amount of nestedOption::None
collapses toOption::Some(Option::Some(...(Option::None)))
.Option::None
find
function could be changed. However, the fact that it is defined as-it-is leads me to the conclusion that idiomatic Kotlin leads to structural loss.Steven Shaw
07/07/2023, 12:36 AM