Pipe-forward operator |>

I know, but with your solution one should maintain a zoo of extension functions from project to project for different set of libraries. With appropriate import option it should became less messy.

After fiddling with the problem, I finally wrote those:

fun add(x: Int): Int = x + 1
fun mul(x: Int): Int = x * 12

class Pipe<out A>(val v: A) {
    inline infix fun <B> andThen(function: (A) -> B) = Pipe(function(v))
}

infix fun <T, R> T.into(other: (T) -> R) = Pipe(other(this))

fun main(args: Array<String>) {
    5 into ::add andThen ::mul andThen ::println
}

I think it works quite great without adding anything to Kotlin itslef

You do not need the Pipe class for that. You do always create an object for each result, but why?

infix fun <T, R> T.into(func: (T) -> R) = func(this)

should be sufficient. It looks like that:

fun main(args: Array<String>) {
     args[0].toDouble() into ::sqrt into Double::inc into ::println
}

I think, that would make the operator pretty much useless, because it is just one line of code enabling the rail (or pipe or whatever).

It can be done in many ways, below code worked very well with me:

package hello

fun main(args: Array<String>) {
    var a = 3
    val b: Int = 6


    println("Hello World! doubling, then tripling the sume of $a and $b is " +
            "${(sumx(a,b)
                    next ::dbl
                    next ::trpl
                    )}")

    println("Hello World! doubling, then tripling the sume of $a and $b is " +
            "${(sumx(a,b)
                    .let (::dbl)
                    .let (::trpl)
                    )}")
    println("Hello World! doubling, then tripling the sume of $a and $b is " +
            "${(sumx(a,b)
                    .run (::dbl)
                    .run (::trpl)
                    )}")

    println("Hello World! doubling, then tripling the sume of $a and $b is " +
            "${(sumx(a,b)
                    into (::dbl)
                    into (::trpl)
                    )}")
}

fun sumx (x: Int, y: Int) : Int = x + y
fun dbl (x: Int): Int = x * 2
fun trpl (x: Int): Int = x * 3

infix fun <T, R> T.next(map : (T) -> R) : R = map(this)
infix fun <T, R> T.into(func: (T) -> R) = func(this)

The output is same in all cases, as:

Hello World! doubling, then tripling the sume of 3 and 6 is 54
Hello World! doubling, then tripling the sume of 3 and 6 is 54
Hello World! doubling, then tripling the sume of 3 and 6 is 54
Hello World! doubling, then tripling the sume of 3 and 6 is 54

FWIW, a pipe-forward operator would be very much welcome, and it seems within the idiom of the language.

For example, the coroutine documentation makes references to pipelines – it would be useful to be able to pipe coroutines together
|> coroutine1
|> coroutine2
|> coroutine3

4 Likes

But you can already pipe all relevant coroutine and sequence operations in Kotlin without any special piping operator. This is made possible by extension functions in Kotlin. All the “pipeable” functions in Kotlin idiomatic code are defined as extensions, so you can write s.filter { ... }.map { ... } as a pipeline. Only languages that lack the concept of “extension function” need piping operator to turn regular (non-extension) filter, map, etc functions into a pipeline-looking style.

3 Likes

You can already do it like this with no new declarations and much more readable:

args[0].toDouble()
        .run(::sqrt)
        .run(Double::inc)
        .run(::println)
1 Like

great thread with interesting ideas. Fwiw, I’ve found the pipe operator useful in other languages. The best I can do is similar to a couple of other suggestions:

infix fun <T, R> T.v(fn: (T) -> R): R {
    return fn(this)
}

(1..10) v { it.filter { it % 2 == 0 } } v { it.map { it * 2 } }  

Nowhere near as elegant as the scala or F# equivalent but it suffices.

It would be very useful to allow for operators that are not limited to the alphanumerics that we are limited with.

But why don’t you just use this?

(1..10).filter { it % 2 == 0 }.map { it * 2 }

Elizarov already pointed out that you can do all of those things already. I mean this is what extension functions are made for.

1 Like

The last example was not a correct example, if I’m right.

The reason to include something like this is if you have a function which receives one param.

With a option like this, you don’t need to create an extension function for every single function that accepts only one param.
Instead you can just use one function, like into.

Hack language has a really nice pipe operator, which also supports functions with multiple arguments. Please check: Expressions And Operators: Introduction

Example:

function piped_example(array<int> $arr): int {
  return $arr
    |> \array_map($x ==> $x * $x, $$)
    |> \array_filter($$, $x ==> $x % 2 == 0)
    |> \count($$);
}

Basically $$ is a special variable name used for piping

This is just syntactic sugar, it doesn’t add any complexity to the language itself

How about this

request
  .let(::validate)
  .let(::persist)
  .let(::notify)
  .let(::getResponse);
1 Like
val pipeline = listOf(
  ::sanitize,
  ::validate,
  ::parse,
  ::process,
  ::optimize,
  ::publish
)

val result = pipeline.fold(input) { output, stage -> stage.invoke(output) }

// You can hide this building block away behind a function
6 Likes

Nice idea @oluwasayo!

fun <T> pipeline( vararg functions :(T)->T ) :(T)->T = { functions.fold(it) { output, stage -> stage.invoke(output) } }
3 Likes

Don’t the solutions proposed here which involve let/apply/run, as well as the more exotic ones, carry huge performance overhead?

And for such a basic construct as calling functions.

(side point: new version of IDEA emits warnings on using let with a single statement)

A simple approach with treating |> as a syntactic sugar at the compiler level, and just rearranging function calls, is much more effective.

Those functions are declared as inline functions. Using them is not less efficient than inclining their functions bodies.

The warning for single statement let is supposedly just for reducing verbosity where possible.

3 Likes

Good to know! I have been using them a lot recently :smile:

@fvasco, to give your extension of @oluwasayo’s idea a concrete example:

val result = pipeline(
  ::sanitize,
  ::validate,
  ::parse,
  ::process,
  ::optimize,
  ::publish
)

This is indeed pretty nice and would avoid the uglyness of |> (which could easily win the “ugliest Kotlin operator” award). Plus, pipeline would be self-explanatory, whereas |> looks like some Perl programmer came to life again.

4 Likes

That one only works when the type never changes throughout the pipeline. For a general solution, you would need an overload for every possible number of functions:

fun <T0>         pipeline()                                : (T0) -> T0 = { it }
fun <T0, T1>     pipeline(f1: (T0) -> T1)                  : (T0) -> T1 = { f1(it) }
fun <T0, T1, T2> pipeline(f1: (T0) -> T1, f2: (T1) -> T2)  : (T0) -> T2 = { f2(f1(it)) }
/* insert 253 more of these */
3 Likes

The newest kotlin plugin will raise a warning in code inspections

This inspection detects a redundant let when it includes only one call with lambda parameter as receiver.

Why could this happen?