Having trouble with lambdas and function links strange behavior.
See comments in code:
class User() {
var i = 0
fun simpleFoo() {
println("Unit")
}
}
class Either {
lateinit var b: User
fun foo(fnR: (User) -> Any): Any = fnR(b)
}
class A {
fun main() {
val either = Either<User>()
either.foo(::handleUser) // works fine
either.foo() { user ->
user.i = 3 // Expected value of type Any
}
}
fun handleUser(user: User) {
user.simpleFoo()
}
}
This happens because assignment in Kotlin is not expression. So it doesn't return Unit.
Workaround is to just use function link
either.foo(::handleUser) // works fine
Function link like either.foo(::handleUser) is fine but if you want to use lambda expression then in your case you did a mistake. Your function
fun foo(fnR: (User) -> Any): Any = fnR(b)
receives a function that returns a value of type Any so you must have to return a value of type Any from your lambda (you forgot to return a value) like:
either.foo() { user ->
user.i = 3 // Expected value of type Any
user // You should return a value as Any and here I returned user as Any
}
Related
I'm trying to get a class similar to this (contrived) example to compile:
class Foo<T> {
val value: T
val condition: Boolean
fun <R> transform(func: () -> R): R {
return if (condition) {
func()
} else {
// Type mismatch: required: R, found: T
value
}
}
}
The transform method can return either value or the result of func(), so T should be assignable to R. Is there a way to express this in Kotlin?
I tried using where T : R, but the compiler doesn't understand that T should refer to the class's T. An extension function could work, but I want to avoid that because it complicates Java interoperability.
You can try this , it works. You need to pass two type params while initializing.
class Foo<T:R,R> constructor(val value: T,val condition: Boolean) {
fun transform(func: () -> R): R {
return if (condition) {
func()
} else {
value
}
}
}
Example:
var s = Foo<String,CharSequence>("12",false).transform {
"as"
}
You pass "12" as string . Transform return "as" value as CharSequence .
Update:
As far as I know, only using extension function might be solve your requirement.
Here is the extension function solution.
class Foo<T> constructor(val value: T,val condition: Boolean){}
fun <T:R,R> Foo<T>.transform(func: () -> R):R{
return if (condition) {
func()
} else {
value
}
}
Example of using extension function solution.
fun main() {
var s1 = Foo(setOf("Hello"),false).transform<Set<String>,Iterable<String>> {
setOf("World")
}
var s2 = Foo(listOf("Hello"),false).transform<List<String>,Iterable<String>> {
listOf("World")
}
}
You just use the type parameter from the class directly. Your method doesn't need to introduce another type parameter:
class Foo<T> {
val value: T
val condition: Boolean
fun transform(func: () -> T): T {
return if (condition) {
func()
} else {
value
}
}
}
I want to allow any one of these two return type (ApiResponse || ErrorResponse). But Return Type should not be a object or Any.
fun getAllUser() : Any? {
val flag = true
return if(flag){
ApiResponse(true)
} else {
ErrorResponse(500)
}
}
With return type (Any), Not able to write an extension function to do specific action with with two different return type. I want t specify Two responses.
In My case, I want to write different Extension function for ApiResponse & ErrorResponse class.
Is it possible to return either ErrorResponse or ApiResponse in a same function?
Create a sealed interface that both of your classes implement:
sealed interface Response<out T>
data class ApiResponse<T>(val data: T): Response<T>
data class ErrorResponse(val errorCode: Int): Response<Nothing>
fun getAllUser() : Response<Boolean> {
val flag = true
return if(flag){
ApiResponse(true)
} else {
ErrorResponse(500)
}
}
Then you can write extension functions that handle either type:
fun Response<Boolean>.foo() {
when (this) {
is ApiResponse<Boolean> -> { TODO() }
is ErrorResponse -> { TODO() }
}
}
Inside the branches of this when statement, the input will be smart cast to the appropriate type.
I got a Idea to return either ErrorResponse or ApiResponse in a same function.
By using [Λrrow][1] library I'm able to achieve this like the following function.
fun getAllUser() : Either<ErrorResponse,ApiResponse>? {
val flag = true
ResponseEntity(ErrorResponse(500),HttpStatus.INTERNAL_SERVER_ERROR)
ResponseEntity.internalServerError().build<ErrorResponse>()
val response: Either<ErrorResponse,ApiResponse> = return if(flag){
Either.right(ApiResponse(true))
} else {
Either.left(ErrorResponse(500))
}
return response
}
I suggest using Result class. Either the one provided by Kotlin or, even better, your own implementation. Here is one of the examples of using custom implementation, along with the explanation. This should give you all the information you need.
using kotlin, having code
fun fetchRemoteDataApi(): Single<RemoteDataResponse> = networkApi.getData()
// it is just a retrofit
#GET(".../api/getData")
fun getData() : Single<RemoteDataResponse>
fun mergeApiWithDb(): Completable = fetchRemoteDataApi()
.zipWith(localDao.getAll())
.flatMapCompletable { (remoteData, localData) ->
doMerge(remoteData, localData) //<== return a Completable
}
the code flow:
val mergeApiDbCall = mergeApiWithDb().onErrorComplete().cache() //<=== would like do some inspection at this level
PublishSubject.create<Unit>().toFlowable(BackpressureStrategy.LATEST)
.compose(Transformers.flowableIO())
.switchMap {
//merge DB with api, or local default value first then listen to DB change
mergeApiDbCall.andThen(listAllTopics())
.concatMapSingle { topics -> remoteTopicUsers.map { topics to it } }
}
.flatMapCompletable { (topics, user) ->
// do something return Completable
}
.subscribe({
...
}, { throwable ->
...
})
and when making the call
val mergeApiDbCall = mergeApiWithDb().onErrorComplete().cache()
the question is if would like to inspect on the Singles<RemoteDataResponse> returned from fetchRemoteDataApi() (i.e. using Log.i(...) to printout the content of RemoteDataResponse, etc.), either in got error or success case, how to do it?
/// the functions
fun listAllTopics(): Flowable<List<String>> = localRepoDao.getAllTopics()
// which a DAO:
#Query("SELECT topic FROM RemoteDataTable WHERE read = 1")
fun getAllTopics(): Flowable<List<String>>
///
private val remoteTopicUsers: Single<List<User>>
get() {
return Single.create {
networkApi.getTopicUsers(object : ICallback.IGetTopicUsersCallback {
override fun onSuccess(result: List<User>) = it.onSuccess(result)
override fun onError(errorCode: Int, errorMsg: String?) = it.onError(Exception(errorCode, errorMsg))
})
}
}
You cannot extract information about elements from the Completable. Though you can use doOnComplete() on Completable, it will not provide you any information about the element.
You can inspect elements if you call doOnSuccess() on your Single, so you need to incorporate this call earlier in your code. To inspect errors you can use doOnError() on both Completable or Single.
I have a function that catches recoverable exceptions and returns a fallback
private fun <T> safely(block: () -> T, fallback: T): T {
return try {
block()
} catch(exc: SomeException) {
// Log the exception/do some logic
fallback
}
}
I want to be able to add this to the public methods of my class e.g.
fun doSomething(): List<String> = safely({
val list = mutableListOf<String>("Hello")
fun someCheck1() = false // Some boolean value
fun someCheck2() = true // Some boolean value
do {
if(someCheck2()) {
return arrayListOf<String>("Hello", "World")
}
} while (someCheck1())
return list
}, arrayListOf<String>())
However I get compiler errors 'return' is not allowed here
Yet if I remove the return then my return in the loop no longer works and it gets highlighted in my IDE with warning the expression is never used
How can I maintain this type of return logic within a Lambda?
Playground Example
Try
fun doSomething(): List<String> = safely(
{
val list = mutableListOf<String>("Hello")
fun someCheck1() = false // Some boolean value
fun someCheck2() = true // Some boolean value
do {
if (someCheck2()) {
return#safely arrayListOf<String>("Hello", "World")
}
} while (someCheck1())
list
}
, arrayListOf<String>())
For further reference, check Using return inside a lambda?
Or you can also extract your block into a separate function (i.e. someCheckFunction(): List<String>), and have fun doSomething() = safely({ someCheckFunction() }, arrayListOf()), but I guess you want to maintain lambda code like above.
return arrayListOf<String>("Hello", "World") here tries to return a value from doSomething function rather than from the lambda passed to safely. However, such return is non-local, since it tries to exit from the function that is not on the top of stack, and therefore it is prohibited.
Another option here is to make safely function inline:
inline fun <T> safely(block: () -> T, fallback: T): T { ... }
and then you'll be able to make a non-local return from block lambda function passed to it.
With all the well-known single-function listeners we can use a simpler lambda notation
view.setOnClickListener { do() }
instead of the original, longer Java way of
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
do()
}
})
But what exactly makes this work? I tried to do the same with my own listener:
private var listener: OnCopyPasteClickListener? = null
interface OnCopyPasteClickListener {
fun onPasteClick(text: String)
}
fun setOnCopyPasteClickListener(onCopyPasteClickListener: OnCopyPasteClickListener) {
listener = onCopyPasteClickListener
}
and while the long approach works just fine:
copypaste.setOnCopyPasteClickListener(object : CopyPasteMenu.OnCopyPasteClickListener {
override fun onPasteClick(text: String) {
do(text)
}
})
I can't make it accept the short one:
copypaste.setOnCopyPasteClickListener {
do(it)
}
The IDE gives a type mismatch error.
Actually, if you have only one function to be invoked, I recommend you use Kotlin Callback.
typealias OnDoWorkListener = ((String) -> Unit)
class Work {
var doWork: OnDoWorkListener? = null
fun doSomething() {
doWork?.invoke("Message Here")
}
}
And in your function, you just set the callback to it
fun main() {
val work = Work()
work.doWork = {
Log.d("WORK", "This gets called from the `work` object. Message: $it")
}
work.doSomething();
}
We can also use function to set the listener as well.
class Work {
var doWork: OnDoWorkListener? = null
fun doSomething() {
doWork?.invoke("Message Here")
}
fun setOnWorkListener(listener: OnDoWorkListener) {
doWork = listener
}
}
fun main() {
val work = Work()
work.setOnWorkListener {
Log.d("WORK", "This gets called from the `work` object. Message: $it")
}
work.doSomething()
}
Higher order functions make this work:
Kotlin functions are first-class, which means that they can be stored
in variables and data structures, passed as arguments to and returned
from other higher-order functions. You can operate with functions in
any way that is possible for other non-function values.
From the same page:
Passing a lambda to the last parameter
In Kotlin, there is a convention that if the last parameter of a
function accepts a function, a lambda expression that is passed as the
corresponding argument can be placed outside the parentheses:
val product = items.fold(1) { acc, e -> acc * e }
If the lambda is the only argument to that call, the parentheses can
be omitted entirely:
run { println("...") }
Knowing this, a possible update on your class would look like:
class CopyPaste {
private var listener: (String) -> Unit = {}
fun setOnCopyPasteClickListener(onCopyPasteClickListener: (String) -> Unit) {
listener = onCopyPasteClickListener
}
fun doCopyPaste(value: String) {
listener.invoke(value)
}
}
fun main() {
val copyPaste = CopyPaste()
copyPaste.setOnCopyPasteClickListener { println(it) }
copyPaste.doCopyPaste("ClipboardContent!")
}
The class CopyPaste stores the listener, which is a function that takes a String parameter and does not return anything. Its function setOnCopyPasteClickListener accepts a function with the same signature as the listener property and at the end doCopyPaste accepts a String parameter and passes it to the stored function.
Actually, just after I posted, I searched for more thoughts and found this thread: https://youtrack.jetbrains.com/issue/KT-7770 This is indeed a debated limitation as it currently only applies to Java, not Kotlin itself. There is also a suggestion there that gives almost the required simplicity:
interface OnCopyPasteClickListener {
fun onPasteClick(text: String)
companion object {
inline operator fun invoke(crossinline op: (text: String) -> Unit) =
object : OnCopyPasteClickListener {
override fun onPasteClick(text: String) = op(text)
}
}
}
and then, thanks to this overloaded operator, it can be called as:
copypaste.setOnCopyPasteClickListener(CopyPasteMenu.OnCopyPasteClickListener { text ->
do(text)
})
But as the suggested answers offer a more idiomatic solution, I'll accept one of those, I only wanted to include this approach here for reference.