This is an extension function:
fun <T, R> Collection<T>.fold(initial: R, combine: (acc: R, nextElement: T) -> R): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
Is it possible to replace the second parameter which is a function with a separate function. For example, something that would look similar to this:
fun <T, R> Collection<T>.fold(initial: R, someFun)
fun someFun (acc: R, nextElement: T) -> R): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
You can use two colons to pass reference to the function:
var collection = listOf<String>()
collection.fold(3, ::someFun)
fun <T, R> someFun(acc: R, nextElement: T): R {
var accumulator: R = acc
// ...
return accumulator
}
I'm not sure why do you need to extract a function this way. The desired code in question does not compile and to propose a working alternative it's required to know your actual intent.
For example if you don't want to spell a long function signature in the type of a parameter, perhaps because you have a lot of such functions taking that type of function parameter and you afraid of making a mistake in that signature, you can extract the functional type declaration into a type alias:
typealias Combiner<R, T> = (acc: R, nextElement: T) -> R
and then use that type alias in the function declaration:
fun <T, R> Collection<T>.fold(initial: R, combine: Combiner<R, T>): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
Related
I am trying to create a function that has two generic types: one reified, and another derived from the context of its usage (since it is an extension function):
inline fun <reified E, A> Either<Throwable, A>.bypassLeft(transformation: Throwable.() -> A): Either<Throwable, A> =
when (this) {
is Either.Left -> when (value) {
is E -> value.transformation().right()
else -> this
}
else -> this
}
The idea would be to call the function just mentioning the reified type, something like:
a.bypassLeft<NoResultException> { "" }
In which "a" is an object of type Either<Throwable,String>
But the compiler is not letting me go away with it, and requires me to specify both generic types, instead of deriving the second one form the object calling the function.
It seemed quite a reasonable thing to be possible, but maybe I am wrong...
Is this possible to achieve? If so, what am I doing wrong?
It's not currently possible with a function to ascribe a single type argument and leave the other inferred. You can achieve what you want if you type the lambda arguments by changing your implementation to not use a receiver type.
I threw in there an additional impl that shows how type args can also be partially applied with a class or other surrounding scope.
import arrow.core.Either
import arrow.core.right
inline fun <reified E : Throwable, A> Either<Throwable, A>.bypassLeft(
transformation: (E) -> A //changed to regular arg not receiver
): Either<Throwable, A> =
when (this) {
is Either.Left -> when (val v = value) { //name locally for smart cast
is E -> transformation(v).right()
else -> this
}
else -> this
}
class Catch<A>(val f: () -> A) { //alternative impl with partial type app
inline fun <reified E : Throwable> recover(
recover: (E) -> A
): Either<Throwable, A> =
Either.catch(f).fold(
{
if (it is E) Either.Right(recover(it))
else Either.Left(it)
},
{
Either.Right(it)
}
)
}
suspend fun main() {
val x: Either<Throwable, Int> = Either.Left(StackOverflowError())
val recovered = x.bypassLeft {
s: StackOverflowError -> //here infers E
0 // here infers A
}
println(recovered) // Either.Right(0)
val notRecovered: Either<Throwable, Int> =
Catch {
throw NumberFormatException()
1
}.recover<StackOverflowError> { 0 }
println(notRecovered) // Either.Left(java.lang.NumberFormatException)
}
This is possible as of Kotlin v1.7.0 with the underscore operator.
The underscore operator _ can be used for type arguments. Use it to automatically infer a type of the argument when other types are explicitly specified:
interface Foo<T>
fun <T, F : Foo<T>> bar() {}
fun baz() {
bar<_, Foo<String>>() // T = String is inferred
}
In your example, it would be possible like this:
a.bypassLeft<NoResultException, _> { "" }
Why it won't compile? It tells there's some error in list.sortBy
fun <T, R : Comparable<R>> Iterable<T>.sortBy(vararg selectors: (T) -> R): List<T> {
return this.sortedWith(compareBy(*selectors))
}
fun main() {
class Person(val name: String, val age: Int)
val list = listOf(Person("Alex", 20))
val sorted = list.sortBy({ it.name }, { it.age })
println(sorted)
}
The error
Type parameter bound for R in
fun <T, R : Comparable<R>> Iterable<T>.sortBy
(
vararg selectors: (T) → R
)
: List<T>
is not satisfied: inferred type Any is not a subtype of Comparable<Any>
When it tries to infer the type R from the first lambda, it's a Comparable<String>. The second lambda returns an Int or Comparable<Int>, which is not a Comparable<String>, so it fails.
You can use star projection for the Comparable type since it doesn't matter if they match.
fun <T> Iterable<T>.sortBy(vararg selectors: (T) -> Comparable<*>): List<T> {
return this.sortedWith(compareBy(*selectors))
}
So I'm trying to reduce this code and avoid the smart cast hint from IDE.
The idea is I have a nullable variable of type T and I want to either map it to R or I just get R from a supplier in case the variable is null.
I've tried different approaches and came up with this one. Still it gives me the smart cast hint.
fun <T, R> T?.func(mapper: (T) -> R, supplier: () -> R): R =
when(this) {
null -> supplier()
else -> mapper(this) // smart cast
}
But I don't like the need for wrapping one of the lambdas in parenthesis. For example.
fun foo(value: String?): Int =
value.func({ it.length + 20}) { 30 }
This may seem odd but the ideia in my context was to pass the variable as not nullable to a function that produced a R or call a function that generated a R.
fun bar(value: T?): R =
when(value) {
null -> func1()
else -> func2(value) // smart cast
}
Note: I've read this but its not the same.
Following should avoid the smart cast hint
fun <T, R> T?.func(mapper: (T) -> R, supplier: () -> R): R {
return this?.let { mapper(it) } ?: supplier()
}
The following does not compile:
fun<T> doSomething(value: T, action: (value: T) -> String = Any::toString){
//do something
}
The error is:
Kotlin: Type mismatch: inferred type is KFunction1<Any, String> but (T) -> String was expected
Making it work is easy:
fun<T> doSomething(value: T, action: (t: T) -> String = {t -> t.toString()}) = action(value)
However, this leaves me wondering: what is the difference between lambdas and KFunctions? Why do we need both?
Also is there a simpler way to provide Any::toString as the default action?
The reason why the code does not compile has nothing to do with the difference between lambdas and KFunctions. It doesn't compile because the parameter needs to be a function of type (T) -> String, and Any::toString is a function of type (Any) -> String.
When you obtain any function (lambda or otherwise) reference with :: you are using reflection. KFunction is Kotlin's way to to wrap around reflected functions.
As to making Any::toString work - there is a way but you may not like it:
fun <T> doSomething(value: T, action: (t: T) -> String = Any::toString as (T) -> String) {
// ...
}
It would have compiled if do like this:
fun <T> doSomething(value: T, action: (value: Any) -> String = Any::toString) {
//do something
}
or
fun <T : Any> doSomething(value: T, action: (t: T) -> String = Any::toString) {
// ...
}
Kotlin docs states that "functions are first-class". I'm trying to use a function as a default value of a function extension. However the compiler isn't having any of it:
fun <T> identity(x: T): T = x
fun <T, P> Channel<T>.dedupe(by: (T) -> P = ::identity): ReceiveChannel<T>
{
...
}
The error is Function invocation 'identity(...)' expected which kinda indicates Kotlin isn't really understanding what I want to do at all.
Is there a way?
I don't know why you get this error message, but the problem is type mismatch: the default value must make sense for any type parameters (subject to bounds). I.e. you need a (T) -> P, but ::identity can give you (T) -> T or (P) -> P.
Proof: if you change to
fun <T, P> identity(x: T): P = throw Exception()
fun <T, P> List<T>.dedupe(by: (T) -> P = ::identity): Unit {}
it compiles.
Answer (which came out in comments below):
If P is changed to Any?, we should be able to use ::identity because (T) -> T is a subtype of (T) -> Any?. Unfortunately, it doesn't work, but using a lambda instead of a function reference does:
fun <T> identity(x: T): T = x
fun <T> Channel<T>.dedupe(by: (T) -> Any? = { it }): ReceiveChannel<T>
{
...
}