Code for functional pipelines

The following code creates an “operator” |> which makes functional pipelines.
(just like in F#)


extension [A, B](a: A) infix def |>(f: A => B): B = f(a)

The thing is, i cannot read this code , or understand. Can someone explain me this code in wordings ?

Let me explain it in java. in java you can write code like this

public static <A, B> B pipe(A a, Function<A, B> f) {
	return f.apply(a);
}
public static void main(String[] args) {
	System.out.println(Main.<Integer, Integer>pipe(1, x -> x + 2));
}

And the same, in scala, when you are using this form of |> it expands like this

val num = 1
val ret = num |> (x => x + 2)
// following is how it works
num.|>(x => x + 2)
|>(num)(x => x + 2)
// note that scala supports curring invocation, it's something like following but not equal:
|>(num, x => x + 2)

you can see the |> has no difference from the java version except its function name is a symbol.

1 Like

Also note Scala has pipe in scala.util.chaining.
There have been many folks asking about it in the past, for some reason everyone seems to be overlooking it :laughing: Maybe Scala isn’t advertising it well? :laughing:

From the operators module in the F# compiler sources:

let inline (|>) arg func = func arg

That module acts as an extension.

Adding explicit types:

let inline (|>)<'A, 'B> (arg: 'A) (func: 'A -> 'B): 'B = func arg

Renaming:

let inline <'A, 'B>(|>) (a: 'A) (f: 'A -> 'B): 'B = f a

f a is function application, but we can add some redundant parentheses:

let inline <'A, 'B>(|>) (a: 'A) (f: 'A -> 'B): 'B = f(a)

This is essentially what you see in the Scala definition. An extension defining a generic function that reverses the operands of a function application.

Have to admit, I am so rusty on F# and OCAML syntax these days, it’s been a long time…

1 Like

FWIW, I know about pipe in scala.util.chaining, but I refrained from it because it’s not an inline function yet.

Scala 3 has had to keep compatibility with Scala 2.13 in the standard library, which is great actually, and I’d rather have that. But ALAS, the standard library also has plenty of instances in which Scala 3’s features could be an improvement. Another example for me would be the use of untagged union types (especially in Option’s or Either’s APIs).

So, I’m not sure if anything can be done about that without breaking compatibility with Scala 2.13 (which I’d rather have).

1 Like

Arguably, there ought to have been a student by now to port the optimizer to Dotty. While inline is nice as a language feature, when all you want to do is inline, then an inliner suffices.

Writing inline everywhere, for this use case, is as great a nuisance as writing @tailrec everywhere, and as unnecessary for a simple optimization.

1 Like

@tailrec is just a compiler check, and it’s a good feature, as tail-recursion is a correctness issue. I would ban recursive function calls without @tailrec or the use of a trampoline, if I could.

IMO, having explicit inlining is, too, because such functions need to be designed for it. The compiler needs to be fairly smart to not screw up, as being too eager to inline may undo the optimizations that the JVM itself does. AFAIK such optimizations were pulled out of the Java compiler. And I have mixed experiences with Scala 2’s optimizations.

And just like in the case of @tailrec sometimes we may need a guarantee that the code gets inlined.