Pipes

siiky

2022/01/09

2022/07/07

en

I've been using my own pipe operators for a while. This is the latest definition:

(define (*=> vals . funs)
  (foldl (lambda (val fun) (fun val))
         (apply (car funs) vals)
         (cdr funs)))

(define ((*-> . funs) . vals)
  (foldl (lambda (val fun) (fun val))
         (apply (car funs) vals)
         (cdr funs)))

(define (=> val . funs)
  (foldl (lambda (val fun) (fun val)) val funs))

(define ((-> . funs) val)
  (foldl (lambda (val fun) (fun val)) val funs))

And this is the original definition (with slightly different semantics):

(define (=*> val funs)
  (foldl (lambda (val fun) (fun val)) val funs))

(define ((-*> funs) val)
  (=*> val funs))

(define (=> val . funs)
  (=*> val funs))

(define ((-> . funs) val)
  (=*> val funs))

Comparing with Scheme's `o`:

(o snd fst)
(-> fst snd)

There's no equivalent to Scheme's `compose`.

I've never used =*> and -*> directly (they're there just in case, and as the base for the other two), but I've grown attached to -> and =>.

The reasoning behind the names is simple: think of a function as a "processing pipe". -> is a chain of such pipes, and a pipe on its own, without "contents" -- you have to plug something on one end to get something on the other end. On the other hand, => already has the stuff plugged in, ready to go, so it's fatter.

Some uses:

(map (-> do-this
         and-that)
     some-list)

(=> some-list
    (cute map (-> do-this and-that) <>)
    (cute filter (o not screwed?) <>))

((-> (cute map (-> do-this and-that) <>)
     (cute filter (o not screwed?) <>))
   some-list)

(filter (o not screwed?)
        (map (-> do-this and-that)
             some-list))

(-> do-this and-that) is an unary function -- that's why it can be given to map.

(=> some-list ...) evaluates to a value, which is the result of applying the filter to the result of applying the map to some-list.

The third and fourth expressions, ((-> ...) some-list) and (filter ...), are equivalent to the second.

Note the use of o instead of -> in the filter's predicate. Personal preference, but I think that case reads better with o because it's more like English.

-----

But now that'll probably be the end of them for me.

Yesterday I learned of SRFI-197 -- very cool! There's even an egg for CHICKEN already.

SRFI-197

SRFI-197 egg

And I can rename the exported identifiers to the ones I've been using:

(import
  chicken.module
  (rename
    (only srfi-197
          chain
          chain-lambda)
    (chain =>)
    (chain-lambda ->)))

With that, the previous example is written like so:

(map (-> (do-this _)
         (and-that _))
     some-list)

(=> some-list
    (map (-> (do-this _) (and-that _)) _)
    (filter (o not screwed?) _))

((-> (map (-> (do-this _) (and-that _)) _)
     (filter (o not screwed?) _))
   some-list)

(filter (o not screwed?)
        (map (-> (do-this _) (and-that _))
             some-list))

Maybe the advantage(s) aren't obvious (maybe they're not advantages at all!),

but to me not having to write `cute` for non-unary functions is a plus, even if

I'm now forced to write parenthesis and an underscore on every unary function.

I guess the only situation(s) I don't see myself using it is if I want to avoid

dependencies.