val a: String? = null
val b: String = ""
fun main(vararg args: String){
//if a variable is not null
a?.apply{ ... } //execute on object
a?.also{_->... } //execute in general
//if a variable is null
(a?:b).exec() //execute on similar object
b?:run{ ... } //execute in general
}
So, on this moment, we can make oneliners of most of the nullable actions:
If a variable is not null:
execute function on object
execute function in general
if a variable is not nullable:
execute function on similar object if null
execute function in general
The only thing I am missing is the assignment when something is null.
When the settter is lightweight, we can use a = a?:b.
When the setter is not lightweight or perform side effects, we have a problem using the syntaxis above.
Therefore I prupose to make for the nullable assigner an exception in the assignment and add the following operator:
(a ?:= b).exec() mapping to {a ?: b.also{a = b} }.exec()
an example in the real world:
(onDockListeners ?:= mutableListOf()).apply{}
//which maps to
(onDockListeners?:mutableListOf().also{onDockListeners = it}).apply{}
//or due to not being type inferenced (yet)
(onDockListeners?:mutableListOf<(UIComponent)->Unit>().also{onDockListeners = it}).apply{}
Unfortunately for your suggested syntax, though, there currently exists no conventional syntax for an operation only performed if an operand is null; only for if it isn’t. Per the current pattern, a ?= b would mean “if a is not null, assign b to it”, whereas what you want it to mean is “if a is null…”.
Even if were agreed that your use case would make more sense to have that operator for, I don’t see Kotlin using the same syntax pattern to mean something different in just one case out of convenience. The Kotlin team has kept a very respectable commitment to thinking beyond their self, to only do things when a proper way to accomplish it can be found.
Wow. Well that’s an interestingly unexpected countersuggestion for a syntax…
Maybe when I’m at a desktop and can better see if it is awkward or clunky when used in actual code, I’ll post some more feedback.
In the meantime, I’ll toss out some quick suggestions:
Possibly quickly review the code excerpts you posted. Although my brain was fried at the time, I remember that when I read your post, the “mappings” and other code appeared to either be incorrect (meaning I didn’t believe they actually did what you expected them to do), or I was alternatively just missing what it was you were trying to show us. Again, my brain was very tired at the moment, so maybe not. If so, you’ll want to fix the code so people don’t act like humans and ignore your suggestion for the wrong reasons.
Additionally, if this is important to you, express through more than one example why this would make your day coding in Kotlin better. Different coders use different coding patterns and may not immediately understand that this is something that would be useful, simply because it doesn’t affect their personal coding routines – or because they don’t realize that it does. Describe a few scenarios (including code) where, for example, the setter is an expensive operation, to hopefully get some more recognition from the current onlookers. (Bonus points if, as it might have looked like earlier, the scenario that has you wanting this is a natural part of working with Android and thus affects an entire invested community)
Btw, that was one of the motivations to add also to the standard library. This way you can replace:
if(a != null) a = func()
a!!.exec()
with a one-liner
(a ?: func().also { a = it }).exec()
You suggest to further simply it to:
(a ?:= func()).exec()
It definitely “reads” properly, though I, personally, is not yet convinced that is better enough to warrant addition of a special syntactical construct (even stdlib function also was debatable). Anyway, I’d suggest you to post your idea to issue tracker: http://kotl.in/issue
P.S. I can see that the chief argument against this new construct is going to be the fact that all Kotlin’s assignments (including compound assignment operators) are not expressions. You cannot write (a = b).exec() in Kotlin, so making only “elvis assignment operator” ?:= to be used as expression would be quite radical and inconsistent.
If you make one-liners and a minimal number of variables goals, you will get difficult to read code like this IMHO. The assignment happens somewhere in the middle here. This is a side effect, and those should be avoided:
My analysis is this:
I have a variable that may be null.
If it is null, it must be given a default value, so I have a variable that cannot be null.
I want to invoke a function on the second variable.
And so I would write it as this. Yes, 2 lines (each having its own purpose) and an additional variable, but the intention is very clear and the code is very short and readable:
When guaranteeda is a field, you need to have additional code:
Var temp = a? : func()
a = temp
temp.exec()
With “one-liners” i would write it like:
(a? : func().also{
a = it;
}).exec()
which looks the same.
with the operator, it would look like
(a ?:= b).exec()
Which I personally find clearer.
Now, Iets say we need to do two things with func. Then your code will be:
var temp = a
if(temp == null){
temp = func()
temp.setup()
temp.start()
}
a = temp
temp.build("dog")
While the special syntaxis uses:
(a?: func().also{
a = it
it.setup()
it.start()
}).build("dog")
Which for me is clearer.
The thing I don’t like about this is that assigning the return type to a is now one of the things done on func, during the building of something similar to a.
With the operator it would look like
(a ?:= func().also{
it.setup()
it.start()
}).build("dog")
Where the assignment is removed from the “setting-up-block” , to a more important position.
If the variable that may be null is a field, I think you should change the design and add a wrapper that will create the default value when the value is retrieved but has not been set yet. Something like initialValue() from TheadLocal. Yes, again way more code than what you want, but with a very clear intent, and using the powerful language features already present in Kotlin:
class X {
private val guaranteedA = GuaranteedValue<AType> {
func().apply {
setup()
start()
}
}
fun setA(a: AType) {
guaranteedA.set(a)
}
fun useA() {
guaranteedA.get().build("dog")
}
}
Again, without actual code, I cannot comment on your design. And again, it sounds like you need to put all this value management in separate classes. Yes, this will require additional code, but it will make the code of the value-using class a lot simpler.
The example that you give in the original post is silly: Postponing the allocation of a mutable list because you may never need it. Simply instantiate a list and your variable will never be null. If you really don’t like instantiation, write a simple function. Note: not thread-safe:
private fun getGuaranteedOnDockListeners(): MutableList<UIComponent> {
if (onDockListeners == null) {
onDockListeners = mutableListOf()
}
// !! is a hint that the code is not idiomatic Kotlin
return onDockListeners!!
}
Now you can simply do:
getGuaranteedOnDockListeners().apply {}
Another option to avoid instantiation is to use immutable collections. There will be only 1 instance of an empty collection that can be reused in all situations.
I hate to admit it, but I think you won this round.
I don’t know an example of a class like this where there is no bad design:
class Clazz{
private var fieldVal: A? = null
fun setIfNull(value: A){
fieldVal ?:= value.also{value->
//do A
}.exec()
}
fun setIfNull(value: B){
fieldVal ?:= value.also{value->
//do B
}.exec()
}
fun clear(){
fieldVal = null
}
}
This means oneliners are not superior to normal code, but just a preference-choice.
Having said that, when we want want to use oneliners, to assign something to a nullable field, there are two ways:
a = a ?: b
Here we have problems when:
setA is expensive.
setA has side-effects, even when the same value is set.
we want to continue with a afterwards(we need to use !!
the way to deal with this is
(a ?: func().also { a = it }).exec()
Which is not very clean.
Therefor the operator ?:= is proposed, with the drawback that this will be the only assignment expression in the language…