Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does chaining .map() and .filter() in Kotlin not work as expected?

I am trying to map a list of objects to a list of objects of another type, then filter the list, then map to a list of a third type, just like I would do by chaining streams in Java 8+ (I changed the class and variable names to make it more meaningful but the structure is the same as in my code):

val results: List<AccountDto> = listOfPersons
                .map { person -> getPersonAccount(person) }
                .filter { account ->
                    if(validateAccount(account)){ // validateAccount is a function with boolean return type
                        // do something here like logging
                        return true
                    }
                    // do something else...
                    return false
                }
                .map { account ->
                     toDto(account) // returns an AccountDto
                }

I get a compiler error on the return true and return false statements inside the filter lambda :

Error:(217, 32) Kotlin: The boolean literal does not conform to the expected type List<AccountDto>

If I use an anonymous function for the filter predicate it compiles fine :

.filter (fun(account):Boolean{
                if(validateAccount(account)){ 
                        // do something here like logging
                        return true
                    }
                    // do something else...
                    return false
            })

Why does the type inference fail in that case?

Is it possible to somehow I make it work with just a lambda ?

like image 468
Pierre Henry Avatar asked Dec 04 '22 19:12

Pierre Henry


1 Answers

return returns from the nearest enclosing function or anonymous function (that's the form fun(a1: T1, ..., an: TN): TR = ...) . The mnemonic is that an unqualified return goes up to the nearest fun. A lambda is not an anonymous function, so the return is actually returning from the function that contains the whole map/filter expression. You can use a labelled return:

val results: List<AccountDto> = listOfPersons
                .map(::getPersonAccount)
                .filter { account ->
                    if(validateAccount(account)){
                        // etc.
                        return@filter true
                    }
                    // etc.
                    return@filter false
                }
                .map(::toDto)

A lambda expression is implicitly labelled with the function that it is passed to, so you don't need to add the label explicitly. If you would prefer to have it, you may:

val results: List<AccountDto> = listOfPersons
                .map(::getPersonAccount)
                .filter lam@{ account ->
                    if(validateAccount(account)){
                        // etc.
                        return@lam true
                    }
                    // etc.
                    return@lam false
                }
                .map(::toDto)

But an if/else is itself an expression, so it suffices to just not have returns.

val results: List<AccountDto> = listOfPersons
                .map(::getPersonAccount)
                .filter { account ->
                    if(validateAccount(account)) {
                        // etc.
                        true
                    } else {
                        // etc.
                        false
                    }
                }
                .map(::toDto)
like image 143
HTNW Avatar answered Dec 07 '22 21:12

HTNW