I'm thinking about solutions to the propagation of...
# kontributors
a
I'm thinking about solutions to the propagation of
OrNull
and here is an idea to start a discussion: https://github.com/apatrida/KEEP/blob/master/proposals/OrNull-syntactical-sugar.md
A symbol search in Intellij IDEA returns 500+ symbols matching
*OrNull
in Kotlin runtimes (many in the generated classes), not sure what the actual count is of distinct functions. But it is a lot.
It seems we should be able to infer the correct function in the pair, and not burden the Kotlin developer with all these variations in the function name space, nor of making a choice that appears obvious to them in context.
I saw this coming up in tweets today and @elizarov making a note about "naming" but really this is something we can remove by using the smart compiler we have with great type inference to figure things out for us.
Naming isn't the problem, the lack of language support for different endgame behavior in equivalent null-returning and non-null asserting functions is the issue.
r
hi @apatrida I see the issue but the biggest concern to me is this case:
Copy code
val xyz = people.first { it.age < 30 }      // error, is ambiguous? or default to indefinite (or definitive) variant?
Free type inference would make everything ambiguous forcing users to ascribe types everywhere to disambiguate. In this case as a user I’d prefer the compiler to infer the nullable version by default which is the safe one, but other users which prefer exception throwing api’s like
first()
would prefer this to infer to a non nullable type and blow up with a domain specific exception on the illegal out of bounds access. I do like a lot the possibility to simplify the variants but not sure how to solve the
default
case for resolution in a good way. Another concern it’s this may require a strategy for backwards compatibility as it would be a breaking change.. In any case I think it’s very interesting and worth exploring. Thanks for taking the time. 🙂
a
backwards compatibility would need to default to the strict non null case because the method name would match that case.
that removes both issues, assuming that default made sense. I'd prefer the null as default, but it is the least backwards compatible.
the strict case leads to unexpected exceptions and would force the '??' operator to be used in anger jaja.
looking through code, this ambiguous case comes up a lot if you only consider one line of code at a time, but it does create problems outside that line if other type inference doesn't know what is happening here.
it's an idea, needs more churning.
e
We’ve been discussing the proliferation of
xxx()
and
xxxOrNull()
pairs quite a number of times. The idea that seems to be most accepted by the team is to shorten
xxxOrNull()
to
xxx?()
. However, just getting a shorter name does not feel like “doing enough” to substantiate this feature. It is having to write the implementation twice that’s really ugly. So, what we are really looking at, is a way to get rid of paired implementations, so that your can write the body of the function just once and then call it with or without
?
(in the first case it will throw function-specific exception, in the second case it will return
null
). That would be a real pain-solver, but we don’t have any design on this “write once, use two-ways” idea. It is fine if the design only works for
inline
functions to start with. Suggestions are welcome.
z
@elizarov
It is fine is the design only works for inline functions to start with.
Do you mean 'if' instead of second 'is'?
👌 1
a
I trial the
xxx?
syntax in the doc as an alternative (Alt 4 - but it is on the decl and not the call site), it works, just is less noticeably but fits making a nullable function expectation. Two implementations is wrapping one call with an exception on null in the outer function, but still extra work. You thinking of an annotation to customize the exception, or just have a standard
!!
type no null expected thrown.
So these would be some conditional type function that have the two possible expectations based on the presence of the nullability call or not. No need for the composite implementations if they are truly one set of code.
The
xxx?
is also backward compatible since you can have the compiler allow and deprecate the
OrNull
form which introducing the
xxx?()
call.
I like it, but need to see how it looks in code. I might still prefer a
??
at the end of the call to match the
!!
operator @elizarov because the
xxx?(parms)
or
xxx? { lambda }
just puts in a place we don't expect a null related operator like
?:
or
!!
or
?.
...
xxx(params)??
is better, but then the lambda version isn't as pretty
xxx { lambda }??
but at least fits with the others.
e
It has to be after name. You should still be reading it as “Or Null”. Moreover, it cannot be an operator in a normal sense. You should not be taking code that returns null and converting it into some specific exception. That’s bad for debuggability, because this way you cannot recover any details in the exception (like location, message, etc). It shall a true “variant selector” of the function. You select which variant of the function you want to call -- the one that throws an exception with a detailed message or the one that returns null from the same place in the code.
The more systemic way to think about it, is that you write a function in the “OrElse” variant. That is, with an additional
onError
lambda that contains code on “what to do on error”. The default value for this lambda is provided by an author of the function and it throws a function-specific exception (and can even use lambda parameters to customize the message). So: • You can call this function as
xxx()
without
onError
argument, and it throws an exception on error. • You can call this function as
xxx(onError = …)
with any value of this
onError
lamda that you want to customize error behavior (e.g. return another value on error). • You can call this function as
xxx?()
to supply
onError = { null }
argument for this lambda.
a
works for me
z
The default value for this lambda is provided by an author of the function and it throws a function-specific exception
@elizarov Is it a good to set such default? I find it error-prone because we can forget about the case when it throws an exception during writing, but we cannot forget to check the result if it is null as compiler checks.
Furthermore, replacing
Copy code
someFunction() : T --> someFunctionOrThrow(error = someDefault) : T
someFunctionOrNull() : T? --> someFunction() : T?
may help to reduce strangeness of some names.
a
The
OrThrow
reverses the behavior for backwards compatibility and breaks existing code.
r
Wondering what the syntax for operator invoke would look like.
value?()
could be ambiguous as that may read as
invoke if not null
rather than
invokeOrNull
e
We’ve been discussing
value?.()
syntax for the latter and, indeed it could become confusing if we end up supporting both in the future.
s
Just wondering if a ?? operator might fit in somewhere to this equation, at the very least it must have been considered, and would have symmetry with !!
e
In languages of C# tradition ?? Operator means the same as ?: in Kotlin, so it might be confusing.
s
Yeah agreed, it would definitely be a repurposing and risk confusion. Thought it might be relevant, in terms of a clear symbol/operator dealing with nullness
a
Is it only C#? And if the operator is before the function call their misuse of the operator would be in error after the function call, so it disambiguates by position, and the compiler is unlikely to accidentally do the wrong thing, no?