Добавление побочного эффекта к функции универсальным способом

#kotlin #generics #lambda #delegation #side-effects

#kotlin #дженерики #лямбда #делегирование #побочные эффекты

Вопрос:

Как я могу написать универсальную функцию Kotlin, которая принимает функцию в качестве аргумента и добавляет к ней побочный эффект? Например,

 fun something(one: Int, two: String): String { return "${one}, ${two}" }
fun somethingElse(arg: Array<String>): String { return "${arg}" }

val w1 = wrapped(::something)
w1(42, "hello")

val w2 = wrapped(::somethingElse)
w2(arrayOf("ichi", "ni"))
  

Следующее работает для функций, которые принимают только один параметр:

 fun <A, R> wrapped(theFun: (a: A) -> R): (a: A) -> R {
    return { a: A ->
        theFun(a).also { println("wrapped: result is $it") }
    }
}
  

Чтобы заставить это работать с произвольным количеством аргументов, мне понадобится некоторая конструкция, которая дает мне тип списка аргументов. К сожалению, функция generic не может быть использована, поскольку она принимает только один параметр. Следующее не компилируется:

 fun <A, R> wrapped(theFun: Function<A, R>): Function<A, R> {
    return { args: A ->
        theFun(*args).also { println("wrapped: result is ${it}") }
    }
}
  

Или, может быть, я мог бы использовать varargs ? Похоже, не работает с лямбдами. Или отражение Kotlin?

Комментарии:

1. Возможно только с отражением.

Ответ №1:

Решение с использованием отражения:

 class KFunctionWithSideEffect<R>(private val f: KFunction<R>, private val sideEffect: (R) -> Unit) : KFunction<R> by f {
    override fun call(vararg args: Any?) = f.call(*args).also { sideEffect(it) }

    override fun callBy(args: Map<KParameter, Any?>) = f.callBy(args).also { sideEffect(it) }
}

fun <R> wrapped(theFun: KFunction<R>, sideEffect: (R) -> Unit = { str -> println("wrapped: result is $str") }) =
    KFunctionWithSideEffect(theFun, sideEffect)
  

Использование:

 val w1 = wrapped(::something)
w1.call(42, "hello")

val w2 = wrapped(::somethingElse)
w2.call(arrayOf("ichi", "ni"))
  

Комментарии:

1. Спасибо, кажется, работает. Хотя это выглядит довольно ужасно. Немного грустно, что такого рода вещи очень легко сделать в TypeScript, но не в Kotlin.