What is Dart's equivalent to Kotlin's let? - kotlin

Recently I've been delving into Flutter's ecosystem and Dart has proven itself a neat and simple language.
Currently, I am looking for a best practice to run methods if an optional variable is not null.
In other words, I am looking for something in Dart that is like Kotlin's let operator :
variable?.let {
doStuff();
doABitMoreStuff();
logStuff();
}
Anyone got any ideas or best practices around this?
I've looked into Dart's documentation and have found nothing that would fit my requirements.
King regards,

With the new Dart extension functions, we can define:
extension ObjectExt<T> on T {
R let<R>(R Function(T that) op) => op(this);
}
This will allow to write x.let(f) instead of f(x).

Dart's equivalent would be a null-aware cascade operator:
The Dart approach would be a to use a null-aware cascade:
SomeType? variable = ...
variable
?..doStuff()
..doABitMoreStuff()
..logStuff();
The null-aware cascade works like the normal cascade, except that if the receiver value is null, it does nothing.
You could make your own using a static function though:
typedef T LetCallback<T>(T value);
T let<T>(T value, LetCallback<T> cb) {
if (value != null) {
return cb(value);
}
}
Then used like that:
let<MyClass>(foo, (it) {
})

We can do it with Dart 2.6 or later.
extension ScopeFunctionsForObject<T extends Object> on T {
ReturnType let<ReturnType>(ReturnType operation_for(T self)) {
return operation_for(this);
}
}
usage: https://github.com/YusukeIwaki/dart-kotlin_flavor#let

The difference between x?.let{ } and if (x != null) { } in Kotlin is that the former promotes x to be non-nullable. Since Kotlin has non-nullable types, it prevents you from needing to write x!! to assert the non-nullability inside the block.
Dart doesn't have non-nullable types (yet), so that distinction isn't important in Dart.
Just use if (x != null) { ... }.
If Dart gets non-nullable types, you can be assured that the null check in the condition will also promote x to non-nullable inside the block (if x is a local variable, is not mutated inside the block, other restrictions may apply).
(EDIT: Dart now has nullable types, and x != null promotes x to non-null.)
From your other comments, it sounds like you might be wanting the Kotlin behavior of x?.run { ... } instead, which binds this to x inside the lambda block. There is no corresponding feature in Dart. You cannot override this, it's always bound to the the object that the current method was called on, even inside nested closures which captures the value of this just as they capture other variables.

Using this extension:
extension Ext on Object? {
void ifNotNull(Function() action) {
if(this != null){
action();
}
}
}
You can achieve something similar:
object.ifNotNull(() => {
// safe code
});

I guess a closure does what you want
class Foo {
var x = 42;
void bar() {
() {
print(x);
doStuff();
doABitMoreStuff();
logStuff();
}();
}
}

Even though Dart doesn't have the let like behavior as of Kotlin but we can certainly emulate it with concise and readable code. Maybe something like this:
void main() {
String str = null;
str?.isEmpty ?? Function.apply(() {
print("Hey there you cheeky null valued variable");
}, []);
}

i implemented let function with extension function like this:
extension KotlinLet on Object?{
void let(Function callback ){
if (this != null) {
callback();
}
}
Object? also(){
if (this != null) {
return this;
}
}
}

You can simply use this package kotlin_flavor: https://pub.dev/packages/kotlin_flavor/install

There is no direct equivalent, because there is no need for it in Dart. Dart is a lot more like Java and you often end up with similar solutions.
There is almost no syntactic sugar in Dart. It's supposed to be easy to learn.
Also, Dart does not enforce strict null checks, all variables are nullable, and the language is single-threaded. That's why there is no need for let. Use if instead:
if(variable != null) {
doStuff();
doABitMoreStuff();
logStuff();
}

Related

Passing a method reference of a nullable type idiomatically

I have an interface:
interface A{
fun test(foo:Int,bar:Int)
}
And I have a nullable reference to an implementation of A
val aImpl:A? = .....
I then have a higher order function which receives a nullable function of signature same as test
...
fun higherOrder(f:((a:Int,B:Int)-> Unit)?){ ... }
How do I pass a reference of test function to higherOrder? For example this doesn't work:
higherOrder(aImpl::test) // aImpl is nullable
higherOrder(aImpl?::test) // I'd expect reasonably this to work, but syntax is invalid
This works, but it feels kinda hacky and long. And I was trying to avoid an extra lambda.
higherOrder(aImpl?.let{it::test})
Is there a more idiomatic way of doing this?
As mentioned in the comments there are a few ways of accomplishing this
interface A{
fun test(foo:Int,bar:Int)
}
val aImpl:A? = .....
fun higherOrder(f:((a:Int,B:Int)-> Unit)?){ ... }
// Not very Kotlin
if (aImpl != null) higherOrder(aImpl::test)
// Kotlin, but weird
higherOrder( aImpl?.let { it::test } )
// Very kotlin, maybe a bit more overhead in understanding
higherOrder { a, b -> aImpl?.test(a, b) }

How best to return a single value of different types from function

I have a function that returns either an error message (String) or a Firestore DocumentReference. I was planning to use a class containing both and testing if the error message is non-null to detect an error and if not then the reference is valid. I thought that was far too verbose however, and then thought it may be neater to return a var. Returning a var is not allowed however. Therefore I return a dynamic and test if result is String to detect an error.
IE.
dynamic varResult = insertDoc(_sCollection,
dataRec.toJson());
if (varResult is String) {
Then after checking for compliance, I read the following from one of the gurus:
"It is bad style to explicitly mark a function as returning Dynamic (or var, or Any or whatever you choose to call it). It is very rare that you need to be aware of it (only when instantiating a generic with multiple type arguments where some are known and some are not)."
I'm quite happy using dynamic for the return value if that is appropriate, but generally I try to comply with best practice. I am also very aware of bloated software and I go to extremes to avoid it. That is why I didn't want to use a Class for the return value.
What is the best way to handle the above situation where the return type could be a String or alternatively some other object, in this case a Firestore DocumentReference (emphasis on very compact code)?
One option would be to create an abstract state class. Something like this:
abstract class DocumentInsertionState {
const DocumentInsertionState();
}
class DocumentInsertionError extends DocumentInsertionState {
final String message;
const DocumentInsertionError(this.message);
}
class DocumentInsertionSuccess<T> extends DocumentInsertionState {
final T object;
const DocumentInsertionSuccess(this.object);
}
class Test {
void doSomething() {
final state = insertDoc();
if (state is DocumentInsertionError) {
}
}
DocumentInsertionState insertDoc() {
try {
return DocumentInsertionSuccess("It worked");
} catch (e) {
return DocumentInsertionError(e.toString());
}
}
}
Full example here: https://github.com/ReactiveX/rxdart/tree/master/example/flutter/github_search

Is there an elegant kotlin way of convincing the compiler that a nullable field to which I just assigned a real value can't be null anymore?

I have read that using !! should generally be avoided.
Is there a way to write the following code in a more elegant way without having to add something like obsolete null checks and duplicated or dead blocks of code?
class A(var field: Thing?) {
fun getField(): Thing {
if (field == null) {
field = Thing()
}
return field!!
}
}
Also I don't understand why the compiler requires the !!-'pray-this-isn't-null-operator' to be satisfied in this scenario.
EDIT: Consider that it is important to me that a potential solution uses lazy initialization if the field is null!
Problem
As Enzokie already mentioned in the comments, another thread could have changed field after the null check. The compiler has no way of knowing that, so you have to tell it.
class A(var field: Thing?) {
fun getField(): Thing {
if (field == null) {
field = Thing()
}
// another thread could have assigned null to field
return field!! // tell the compiler: I am sure that did not happen
}
}
Solution (Eager)
In you particular case it would be a good idea to use a parameter f (you could name it "field" too, but I avoided that for clarity) in the constructor (without val/var) and afterwards assign it to a property field to which you assign either f or a new instance of Thing.
This can be expressed really concise with the Elvis operator :? which takes the left hand side if not null and the right hand side of the expression otherwise. So, in the end field will be of type Thing.
class A(f: Thing?) {
val field = f ?: Thing() // inferred type Thing
}
Solution (Lazy)
Since it was mentioned by gidds, if you need to initialize field lazyly you could do it like this using delegated properties:
class A(f: Thing?) {
val field by lazy {
f ?: Thing() // inferred type Thing
}
}
The call site does not change:
val a = A(null) // field won't be initialized after this line...
a.field // ... but after this
How about this?
class A(field: Thing?) {
private lateinit var field: Thing
init {
field?.let { this.field = it }
}
fun getField(): Thing {
if (!this::field.isInitialized) {
field = Thing()
}
return field
}
}
When you define a field, you actually define a variable plus two accessor methods:
val counter: Integer = 0
It is possible to customize the accessor methods by writing this instead:
val n = 0
val counter: Integer
get() = n++
This will execute the n++ each time you access the counter field, which therefore returns different values on each access. It is uncommon and unexpected but technically possible.
Therefore the Kotlin compiler cannot assume that two accesses to the same field return the same value twice. Usually they do, but it is not guaranteed.
To work around this, you can read the field once by copying it into a local variable:
fun count() {
val counter = counter
println("The counter is $counter, and it is still $counter.")
}

Smart casting not working when statement is refactored. How to solve?

I have something simple below (I use when instead of if, as I simplified from some code that uses when)
fun simplePresent(presentable: Presentable?) {
when {
presentable != null -> execute(presentable)
else -> skip()
}
}
fun execute(presentable: Presentable) { // Do something }
It's all good. But when I refactor the checking code out into Function
fun simplePresent(presentable: Presentable?) {
when {
hasPresentable(presentable) -> execute(presentable)
else -> skip()
}
}
fun execute(presentable: Presentable) { // Do something }
fun hasPresentable(presentable: Presentable?) = presentable != null
the smart casting to non-null fail for the value pass to execute function, causing a compile time error reporting required Presentable found Presentable?
How could I prevent that error, while I still retain my refactor code?
Functions are meant to be independent to each other. There is just no constraint to enforce hasPresentable to return true iff presentable is not null at type level.
So it's kind of impossible without Kotlin team deciding to enhance the type system.
Why not using something like presentable?.execute() ?: skip() instead?
If you want to do more checking in hasPresentable you can do this:
fun checkPresentable(presentable: Presentable?): Presentable? =
presentable?.takeIf { do your check here }
fun simplePresent(presentable: Presentable?) =
checkPresentable(presentable)?.execute() ?: skip()
fun Presentable.execute() { }

Kotlin idiom for working with non-null object and non-blank String representation

I have a nullable property (a Java object) that knows how to convert itself to a String, and if this representation is not empty, I would like to do something with it. In Java this looks like:
MyObject obj = ...
if (obj != null) {
String representation = obj.toString();
if (!StringUtils.isBlank(representation)) {
doSomethingWith(representation);
}
}
I'm trying to find the most idiomatic way of converting this to Kotlin, and I have:
with(obj?.toString()) {
if (!isNullOrBlank()) {
doSomethingWith(representation)
}
}
But it still feels like too much work for such a simple operation. I have this feeling that combining let, when, and with I can slim this down to something a bit shorter.
The steps are:
If the object (A) is not null
If the String representation (B) of object (A) is not blank
Do something with (B)
I tried:
when(where?.toString()) {
isNullOrBlank() -> builder.append(this)
}
But (1) it fails with:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: #InlineOnly public inline fun
CharSequence?.isNullOrBlank(): Boolean defined in kotlin.text #InlineOnly public inline fun CharSequence?.isNullOrBlank(): Boolean defined in
kotlin.text
And even if it got past that, (2) it would want the exhaustive else, which I don't really care to include.
What's the "Kotlin way" here?
You can use the (since Kotlin 1.1) built-in stdlib takeIf() or takeUnless extensions, either works:
obj?.toString().takeUnless { it.isNullOrBlank() }?.let { doSomethingWith(it) }
// or
obj?.toString()?.takeIf { it.isNotBlank() }?.let { doSomethingWith(it) }
// or use a function reference
obj?.toString().takeUnless { it.isNullOrBlank() }?.let(::doSomethingWith)
For executing the action doSomethingWith() on the final value, you can use apply() to work within the context of the current object and the return is the same object, or let() to change the result of the expression, or run() to work within the context of the current object and also change the result of the expression, or also() to execute code while returning the original object.
You can also create your own extension function if you want the naming to be more meaningful, for example nullIfBlank() might be a good name:
obj?.toString().nullIfBlank()?.also { doSomethingWith(it) }
Which is defined as an extension to a nullable String:
fun String?.nullIfBlank(): String? = if (isNullOrBlank()) null else this
If we add one more extension:
fun <R> String.whenNotNullOrBlank(block: (String)->R): R? = this.nullIfBlank()?.let(block)
This allows the code to be simplified to:
obj?.toString()?.whenNotNullOrBlank { doSomethingWith(it) }
// or with a function reference
obj?.toString()?.whenNotNullOrBlank(::doSomethingWith)
You can always write extensions like this to improve readability of your code.
Note: Sometimes I used the ?. null safe accessor and other times not. This is because the predicat/lambdas of some of the functions work with nullable values, and others do not. You can design these either way you want. It's up to you!
For more information on this topic, see: Idiomatic way to deal with nullables