0

Currently I am using compose from a library called arrow which has it defined this way.

inline infix fun <IP, R, P1> ((IP) -> R).compose(crossinline f: (P1) -> IP): (P1) -> R = { p1: P1 -> this(f(p1)) }

What I am trying to do is compose functions from a list so I assumed something as simple as this would work.

val add5 = { i: Int -> Option(i + 5) }
val multiplyBy2 = { i: Int -> i * 2 }
fun isOdd(x: Option<Int>) = x.map { y -> y % 2 != 0 }

val composed = listOf(::isOdd, add5, multiplyBy2).reduce { a, b -> a compose b  }

but I get type error:

Type inference failed: Cannot infer type parameter IP in inline infix fun ((IP) -> R).compose(crossinline f: (P1) -> IP): (P1) -> R None of the following substitutions receiver: (Any) -> Any arguments: ((Nothing) -> Any) receiver: (Nothing) -> Any arguments: ((Nothing) -> Nothing) can be applied to receiver: Function1<, Any> arguments: (Function1<, Any>)

so I try:

val composed = listOf<(Any) -> Any>(::isOdd, add5, multiplyBy2).reduce { x, y -> x compose y }

and I get this:

Type mismatch: inferred type is KFunction1<@ParameterName Option, Option> but (Any) -> Any was expected

Type mismatch: inferred type is (Int) -> Option but (Any) -> Any was expected

Type mismatch: inferred type is (Int) -> Int but (Any) -> Any was expected

Any help appreciated. I don't mind if I end up having to write my own version of compose. I just need to be able to compose a list of functions.

edit:

This works no problems:

val composed = ::isOdd compose add5 compose multiplyBy2

I am just trying to achieve the same result should I have a list of functions instead of writing this way.

Bads
  • 774
  • 3
  • 8
  • 20

1 Answers1

3

I find it hard to imagine how a simple compose should work with methods having so different signatures. So first we would have to align the types of the functions. Arrow let's you compose functions if the return type of the first matches the parameter of the second...

Another problem is that isOdd is a Predicate. It is not transforming a value.

If the transformers have a compatible signature you can compose them with e.g. andThen

Here is a version that aligns the types to compose the functions. Note that filter and map are special functions in arrow's Option that allow you to pass transformer functions/predicates

import arrow.core.Option
import arrow.core.andThen
import org.hamcrest.Matchers.`is`
import org.junit.Assert.assertThat
import org.junit.Test

class ComposeTest {

    @Test
    fun shouldCompose() {
        val add5 = { i: Int -> i + 5 }
        val multiplyBy2 = { i: Int -> i * 2 }
        val isOdd = { x: Int -> x % 2 != 0 }

        val composed: (Int) -> Option<Int> = { i: Int -> Option.just(i)
          .filter(isOdd)
          .map(add5.andThen(multiplyBy2))
        }

        assertThat(composed(3), `is`(Option.just(16)))
        assertThat(composed(4), `is`(Option.empty()))
    }
}
Mathias Dpunkt
  • 11,594
  • 4
  • 45
  • 70
  • In other words, you don't think its possible to compose list of functions with different signatures in kotlin? – Bads Aug 24 '18 at 06:56
  • 1
    I cannot imagine how this should work - at least not without some logic about how to transform one type into another. And predicates are so fundamentally different - they filter instead of transforming - so they would need to be composed differently (filter vs map) – Mathias Dpunkt Aug 24 '18 at 07:23
  • A Predicate is transforming from a value to a Boolean, right? – pablisco May 22 '19 at 22:10
  • You are right - my argumentation is at least partially wrong. My point was that signatures need to be compatible when composing functions. for `andThen` the output of the first function needs to be the input of the second. But of course compose is working the other way around and thus `val composed = isOdd compose add5 compose multiplyBy2` actually works. I also got the intend wrong - I thought @Bads wanted to only compute odd values - but actually, he wants to tell if the result is odd... – Mathias Dpunkt May 23 '19 at 20:11