Something Like Pipe Forward In Racket?

Is there anything in Racket like the pipe forward operator (|>) that I've seen in Haskell, F# and Elixir?

For those unfamiliar with this operator it simply passes the output of one function as the input to the next. So this:

(f (g x))

Would become this:

(f x) |> g

Or something like that. I tried creating it myself but no doubt | is reserved for other purposes in Racket. And I'd really like to have it because it can simplify nested calls quite a bit! I'm sure others have already built something like this but I'm not sure what to google for hence I ask here.

There are a couple of libraries for that.
Here is one.

Threading Macros

Alexis King
https://docs.racket-lang.org/threading/index.html

Also, checkout QI.

3 Likes

Do you mean this:


racket

Welcome to Racket v8.15.0.12 [cs].

> (require threading)

> (~> 1 add1 sub1)

1

Also check out Qi

https://docs.racket-lang.org/qi/index.html

2 Likes

Yes, try Qi!

(require qi)
(~> (3) sqr add1) ; => 10

And btw yes, | is reserved -- it's used to read some characters literally, without any special meaning those characters might otherwise have while reading the input. Racket is an S-expression / symex-oriented language, meaning that almost everything has the syntax (f x ...), where f is either a function or a macro. So although you could implement a rule like what you had there:

(f x) |> g --> (f (g x))

... it would need to be done as a reader extension (relatively rare) and not as a simple macro (very common and easy). Since S-expressions are such a versatile and explicit syntax, it's easy to write extensions to the language as rules transforming one S-expression to another and these are what we call macros. Qi is one such implementation of this behavior as a macro. The threading macro is another.

2 Likes

As previously answered it needs a reader extension or external parser and use a library (treading macro or Qi)

You can use as reader extension SRFI 105 curly infix, i have implemented one for Racket, but for now ( see note below) it is simpler to use Scheme+ for Racket

here is the running example:

Welcome to DrRacket, version 8.14 [cs].
Language: reader SRFI-105, with debugging; memory limit: 8192 MB.
SRFI-105 Curly Infix parser for Racket Scheme and R6RS by Damien MATTEI
(based on code from David A. Wheeler and Alan Manuel K. Gloria.)


(require threading)

{1 ~> add1 ~> sub1}
1

{2 + {1 ~> add1 ~> sub1}}
3

'{(f x) ~> g}
'(~> (f x) g)

{3 ~> sqr ~> add1}
10

{3 ~> sub1 ~> sqr ~> add1}
5

note: normally only SRFI 105 is enought to have some infix notation if you enabled :

(define srfi-strict #t) ; enable strict compatibility with SRFI 105

in the source code. (for now it is set to #f)

Note also that in an infix expression the new operator ~> should have a defined an operator precedence rule.

Then the same syntax with ( ) instead of { } could be used.

All that are interesting ideas , i will put them in the next release of Scheme+.

2 Likes

works also with Qi:

(require qi)
{(3) ~> sqr ~> add1}
10
3 Likes

compose1 works too:

> ((compose1 add1 sqr) 3)
10
1 Like

Wow so many good answers on this thread! Thanks to all of you for your advice!

I like typed racket (along with what I've seen of Qi) so I sort of combined them in this little example:

#lang typed/racket

(require qi)

(define (times-ten [n : Number])  (* n 10))
(define (turn-to-string [n : Number])  (number->string n))

(define (demonstrate-qi [n : Number]) (~> (n) times-ten turn-to-string))

Just in case anyone else is curious.

1 Like

Just beware that some Qi flows may not combine well with TR—there's not a "typed Qi" and macros are especially prone to TR issues. See https://github.com/drym-org/qi/issues/18

(As much as I love Qi, the threading package doesn't suffer as much from this problem because it performs a direct textual rewrite. Qi is structured more as a compiler of a complex DSL, so you can split the specification (flow (~> times-ten turn-to-string)) from the application (… n); this means it produces output that is harder for TR to reason about.)

1 Like

Yeah that's why I worked up that example; was wondering if Qi could work with TR. I guess I just happened to find a case that didn't cause that issue! Thanks for making me aware of that little potential landmine.

Or use parendown.

( f #/ g x )
gets read as
(f (g x))

It doesn't even matter what f, g, or x are.
It's just notation.

https://docs.racket-lang.org/parendown/index.html

-- hendrik

2 Likes

To add to what Ben said, it would be wonderful to support typed behavior with Qi, whether that's finding ways to interoperate better with Typed Racket, or developing a typed variant of Qi (which may also play well with TR). There have been a few community members who have expressed interest in this, so maybe with enough data and ideas, it would be feasible to make meaningful progress here. For the moment, if you continue to experiment along these lines, it would be valuable if you could share any experiences or desires (e.g., by reporting them on the Typed Qi issue linked above, or as a new issue).

3 Likes

I'm happy to experiment! I don't believe I know enough about TR, Qi or Racket in general to contribute fixes for problems (yet) but I suppose trying things out and reporting issues I find is also helpful right?

Yes, absolutely! Giving something a name gives us power, etc. :slight_smile:

Btw, with help from some kind folks on Discord, there have been some small steps towards interoperability with TR. I don't know how close we can get, but let's see.

Btw I also noticed that Rhombus has this operator, in case you want to try that.

1 Like