point-free
1 Thrush Combinator
thrush
λ~>
thrush+
~>
thrush*
~>*
thrush-and
λand~>
thrush+  -and
and~>
thrush*-and
and~>*
2 Define forms of function composition
define/  compose
define/  thrush
define/  thrush-and
3 Point-free argument counts
arg-count
define/  arg-count
4 Point-free parallel function composition
join
wind-pre
wind-post
wind
join*
wind-pre*
wind-post*
wind*
5 Definition forms of winding functions
define/  wind
define/  wind-pre
define/  wind-post
define/  wind*
define/  wind-pre*
define/  wind-post*
6 Fixpoint functions
fixpoint?
until-fixpoint
6.12

point-free

Jack Firth <[email protected]>

 (require point-free) package: point-free

Tools for defining point-free functions with very little syntactic overhead.

source code: https://github.com/jackfirth/point-free

1 Thrush Combinator

The thrush combinator is a higher order function that reverses the order of application. It can be seen as the reverse of function composition.

procedure

(thrush f ...)  procedure?

  f : procedure?

procedure

(λ~> f ...)  procedure?

  f : procedure?
Returns a procedure that composes the given functions in a way that is the reverse of using compose. That is, the new procedure gives its arguments to the first f, which gives its result to the second f, and so on to the last f. The result of the last function is the result of the entire thrushed function application. This logic can be interpreted as representing a function as a data flow between the given procedures, since values given to the thrushed function flow from left to right through the given procedures. λ~> is a shorthand, and means the same thing as the more literate thrush form. Note that the thrushed procedure can accept multiple arguments if the first f given to it does.

Examples:
> ((thrush add1 positive?) 0)

#t

> ((thrush + even?) 1 2 3)

#t

> ((thrush string->list length even?) "foo")

#f

procedure

(thrush+ v f ...)  any?

  v : any?
  f : procedure?

procedure

(~> v f ...)  any?

  v : any?
  f : procedure?
Returns the result of giving v to (thrush f ...). This is for expressing data-flow logic in a pointful manner rather than a point-free manner, in cases where constructing the intermediate thrushed procedure and applying it as two seperate instances would be awkward syntactically. This limits the thrushed procedure to only accept one argument. ~> is a shorthand, and means the same thing as the more literate thrush+ form.

Examples:

procedure

(thrush* v ...)  (-> [f procedure?] ... any?)

  v : any?

procedure

(~>* v ...)  (-> [f procedure?] ... any?)

  v : any?
Returns a procedure that accepts procedures and composes them with thrush, then calls the thrushed procedure with v ... as arguments. In essence, this flips the order of evaluation - the arguments to the composed function are given first, and the functions to compose are given second. ~>* is a shorthand, and means the same thing as the more literate thrush* form.

Examples:
> ((thrush* 1 2 3) + even?)

#t

> ((thrush* "foo" "bar") string-append string-length)

6

procedure

(thrush-and f ...)  procedure?

  f : procedure?

procedure

(λand~> f ...)  procedure?

  f : procedure?
Like thrush, performs reverse function composition. However, thrush-and includes the short-circuiting behavior of and, so if any intermediate function returns #f, the entire chain returns #f without continuing to thread the value through the chain.

Examples:
> (define find-odd (curry findf odd?))
> ((thrush-and find-odd add1) '(2 3 4))

4

> ((thrush-and find-odd add1) '(2 4 6))

#f

procedure

(thrush+-and v f ...)  any

  v : any/c
  f : procedure?

procedure

(and~> v f ...)  any

  v : any/c
  f : procedure?

procedure

((thrush*-and v ...) f ...)  any

  v : any/c
  f : procedure?

procedure

((and~>* v ...) f ...)  any

  v : any/c
  f : procedure?
Like thrush+ and thrush*, but with the short-circuiting behavior of thrush-and.

2 Define forms of function composition

syntax

(define/compose name func-expr ...)

Defines name as the result of (compose func-expr ...).

Examples:
> (define/compose symbol-length string-length symbol->string)
> (symbol-length 'foo)

3

> (symbol-length 'bazzz)

5

syntax

(define/thrush name func-expr ...)

Defines name as the result of (thrush func-expr ...).

Examples:
> (define/thrush symbol-length symbol->string string-length)
> (symbol-length 'foo)

3

> (symbol-length 'bazzz)

5

syntax

(define/thrush-and name func-expr ...)

Defines name as the result of (thrush-and func-expr ...).

Examples:
> (define/thrush-and inc-odd (curry findf odd?) add1)
> (inc-odd '(2 3 4))

4

> (inc-odd '(2 4 6))

#f

3 Point-free argument counts

These forms define ways to define anonymous functions in a point-free style while binding a single variable that contains the number of arguments passed to the function. This can be useful for removing boilerplate.

syntax

(arg-count n func-expr)

Returns the function that func-expr evaluates to, with n bound in func-expr to the number of arguments passed to the returned function.

Example:
> ((arg-count n (const n)) 'foo 'bar 'baz)

3

syntax

(define/arg-count name n func-expr)

Defines name as the result of (arg-count n func-expr).

Examples:
> (define/arg-count average n
    (compose (curryr / n) +))
> (average 8 10 12)

10

4 Point-free parallel function composition

Racket functions can accept and return any number of arguments, so there are two ways to combine them - chaining them together in serial with compose, or joining them in parallel with the join function defined in this module. These two primitives can be combined in powerful and expressive ways, making point-free function definitions much simpler in many cases. Note that this has absolutely nothing to do with parallel execution or concurrency, this is purely a handy terminology for talking about function operations.

procedure

(join f ...)  procedure?

  f : (-> any/c any/c)
Returns a procedure that accepts one argument for each f, and returns one value for each f that is determined by calling f on its given argument. This can be thought of as joining the functions (f ...) in parallel.

Examples:
> ((join add1 sub1) 0 0)

1

-1

> ((join string->symbol even?) "foo" 5)

'foo

#f

procedure

(wind-pre f g ...)  procedure?

  f : procedure?
  g : (-> any/c any/c)
Returns a procedure that accepts one argument for each g, calls each g on each argument, then passes the results of all the gs to f. The result of f is then the result of the whole procedure. Conceptually, this is equivalent to (compose f (join g ...)).

Examples:
> ((wind-pre < string-length symbol-length) "foo" 'bazz)

#t

> ((wind-pre + string-length symbol-length) "foo" 'bazz)

7

procedure

(wind-post f g ...)  procedure?

  f : procedure?
  g : (-> any/c any/c)
Opposite of wind-pre, instead of calling each g on the inputs of f, each g is called on the outputs of f. This is therefore equivalent to (compose (join g ...) f).

Examples:
> ((wind-post partition length length) number? '(1 2 3 a 4 5 6 "foo" 8))

7

2

> (define (first-and-second lst) (values (first lst) (second lst)))
> ((wind-post first-and-second string? number?) '(1 2 3 4 5))

#f

#t

procedure

((wind f g ...) h ...)  procedure?

  f : procedure?
  g : (-> any/c any/c)
  h : (-> any/c any/c)
Combination of wind-pre and wind-post. The procedures in gs are used to transform the inputs to f, and the outputs are transformed with hs. This function is defined with partial application, the input transformer functions are given first, then the output ones, then finally the wound function is returned.

Examples:
> (define (sqr x) (* x x))
> (define pythagoras ((wind + sqr sqr) sqrt))
> (pythagoras 3 4)

5

> (pythagoras 5 12)

13

procedure

(join* f)  procedure?

  f : (-> any/c any/c)
Similar to join, but instead of accepting several functions and mapping them one-to-one with the inputs of the returned procedure, it accepts only one function and the returned procedure accepts any number of arguments, maps f to each of them, then returns the results as values. Essentially a version of map that returns multiple values instead of a list.

Examples:
> ((join* add1) 1 2 3)

2

3

4

> ((join* symbol->string) 'foo 'bar)

"foo"

"bar"

procedure

(wind-pre* f g)  procedure?

  f : procedure?
  g : (-> any/c any/c)
Analog of wind-pre using join* instead of join, returns a new procedure that maps g to each of its arguments, then returns the result of calling f with those values. Equivalent to (compose f (join* g)).

Examples:
> (define string< (wind-pre* < string-length))
> (string< "foo" "barrr" "bazzzzz")

#t

> (string< "foooooo" "barrr" "baz")

#f

procedure

(wind-post* f g)  procedure?

  f : procedure?
  g : (-> any/c any/c)
Analog of wind-post using join* instead of join, returns a new procedure that first calls f with its arguments, then maps g to the resulting values and returns the mapped values. Equivalent to (compose (join* g) f).

Examples:
> (define partition-counts (wind-post* partition length))
> (partition-counts symbol? '(a b 1 2 3 4 5 c 5 7 8 d 8 e))

5

9

> (partition-counts even? '(1 2 3 4 5 6 7 8 9 10))

5

5

procedure

(wind* f g h)  procedure?

  f : procedure?
  g : (-> any/c any/c)
  h : (-> any/c any/c)
Analog of wind using join* instead of join, returns a new procedure that first maps g to its inputs, passes the mapped values to f, maps h to the outputs of f, then returns those mapped values. Equivalent to (compose (join* h) f (join* g)).

Examples:
> (define str-append (wind* append string->list list->string))
> (str-append "foo" "bar" "baz")

"foobarbaz"

5 Definition forms of winding functions

These forms allow for short definitions of point-free functions using wind and friends.

syntax

(define/wind id f (pre ...) (post ...))

Definition form of wind. Binds id as a wound form of f, with (pre ...) used as the input transforming functions and (post ...) used as the output transformers.

Examples:
> (define/wind pythagoras + (sqr sqr) (sqrt))
> (pythagoras 3 4)

5

> (pythagoras 5 12)

13

syntax

(define/wind-pre id f pre ...)

Definition form of wind-pre. Binds id as a wound form of f, with (pre ...) used as the input transforming functions.

Examples:
> (define/wind-pre sym-and-num->str
    string-append symbol->string number->string)
> (sym-and-num->str 'foo 123)

"foo123"

syntax

(define/wind-post id f post ...)

Definition form of wind-post. Binds id as a wound form of f, with (post ...) used as the output transforming functions.

Examples:
> (define/wind-post first-true-last-false partition first last)
> (first-true-last-false symbol? '(1 2 a b 3 c 4 5 6 d))

'a

6

syntax

(define/wind* id f pre post)

Definition form of wind*. Binds id as a wound form of f, with pre used as the input transforming function and post as the output transformer.

Examples:
> (define/wind* pythagoras + sqr sqrt)
> (pythagoras 3 4)

5

> (pythagoras 5 12)

13

syntax

(define/wind-pre* f pre)

Definition form of wind-pre*. Binds id as a wound form of f, with post used as the input transforming function.

Examples:
> (define/wind-pre* symbol-shorter
    < (λ~> symbol->string string-length))
> (symbol-shorter 'foo 'bazz 'barrr)

#t

> (symbol-shorter 'blah 'bloo)

#f

syntax

(define/wind-post* f post)

Definition form of wind-post*. Binds id as a wound form of f, with post used as the output transforming function.

Examples:
> (define/wind-post* firstf partition first)
> (firstf symbol? '(1 2 3 a b 4 5 c 6 d e))

'a

1

6 Fixpoint functions

These functions manipulate other functions based on their fixpoints - the values that can be given to the function such that the function does nothing and returns just those values. The fixpoints of the function abs for example, are all nonnegative numbers. The absolute value of a nonnegative number x is just "x".

procedure

(fixpoint? f v)  boolean?

  f : (-> any/c any/c)
  v : any/c
Returns #t if v is a fixpoint of f, that is if (f v) is eq? to v. This function must necessarily call f, so avoid using side-effecting functions and expensive functions unless they memoize or cache their calls.

Examples:
> (fixpoint? abs 10)

#t

> (fixpoint? abs -10)

#f

procedure

((until-fixpoint f) v)  any

  f : (-> any/c any/c)
  v : any/c
Returns a procedure that accepts one argument v and applies f to it. If v is not a fixpoint of f, then f is applied to (f v), and again and again recursively until it reaches a value that is a fixpoint of f.

Examples:
> (define (count-once-to-ten n)
    (if (< n 10)
        (begin (displayln n)
               (add1 n))
        n))
> (count-once-to-ten 5)

5

6

> (define count-to-ten (until-fixpoint count-once-to-ten))
> (count-to-ten 5)

5

6

7

8

9

10