Useful Tacit function
1 Some renamed identifier
id
°
~
~~
:
2 The different variant of the fork-macros
fork
fork2
fork3
fork*
3 Syntactic sugar for mutable objects
define-vector
define-mutable-hash
4 Showcase:   Y-Combinator
5 Showcase:   Easy Racket
9.0.0.3

Useful Tacit function🔗ℹ

Laurent Müller <loeru@pm.me>

 (require tacit) package: Tacit

source code: https://github.com/lurry-m/tacit

1 Some renamed identifier🔗ℹ

Firstly, there are some shortcuts for renamed procedures.

value

id : procedure? = values

value

° : procedure? = compose

value

~ : procedure? = curry

value

~~ : procedure? = curryr

value

: : procedure? = const

2 The different variant of the fork-macros🔗ℹ

These macros are inspired by APL (J).

syntax

(fork (first ...) second ...)

Returns the a unary function that applies the all the second arguments to the input and then the first arguments on the result. fork can be used in various ways, because the content of the fork is not restricted to procedures.

Examples:
> (define average (fork (/) sum length))
> (average (range 10))

9/2

> (define euler-form (fork (make-rectangular) cos sin))
> (euler-form pi)

-1.0+1.2246467991473532e-16i

> (define positive-even-number? (fork (and) number? positive? even?))
> (map (fork (cons) id positive-even-number?)
       (range 5))

'((0 . #f) (1 . #f) (2 . #t) (3 . #f) (4 . #t))

> (define displayln-and-negate
    (fork (begin) displayln -))
> (map displayln-and-negate (range 3))

0

1

2

'(0 -1 -2)

syntax

(fork2 (first ...) second ...)

Similar to fork, returns the a binary function that applies the all the second arguments to the input and then the first arguments on the result.

Example:
> ((fork2 (list) + * vector) 5 7)

'(12 35 #(5 7))

syntax

(fork3 (first ...) second ...)

Similar to fork, returns the a ternary function that applies the all the second arguments to the input and then the first arguments on the result.

Example:
> ((fork3 (list) + * vector) 5 7 11)

'(23 385 #(5 7 11))

syntax

(fork* (first ...) second ...)

Similar to fork, but the procedure-arity is identical to the arguments provided in second. Each argument in second is applied to one of the arguments in order.

Examples:
> (define sqr-and-subtract (fork* (-) sqr sqr sqr))
> (procedure-arity sqr-and-subtract)

3

> (define pythagorean-triple? (° zero? sqr-and-subtract))
> (pythagorean-triple? 5 4 3)

#t

> (pythagorean-triple? 6 4 3)

#f

> (define square-and-sum (~ foldl (fork* (+) sqr id) 0))
> (sum (map sqr (range 10)))

285

> (square-and-sum (range 10))

285

> (foldl (fork* (+) id /) 1 '(15 7 3))

355/113

The fork* is useful in combination with foldl. The square-and-sum is more performant and the last line is an example of how to calculate the continued fraction of pi: [3;7,15,1].

3 Syntactic sugar for mutable objects🔗ℹ

Vectors and hashes are useful but they can be a bit verbose. These two definitions provide a few helpful procedures out of the box wich are similar to struct.

syntax

(define-vector identifier my-vector)

This will create a few extra procedures, which might be useful. These are identifier-set!, identifier-ref, identifier-update!, identifier++, identifier-- and identifier-length.

Examples:
> (define-vector v (make-vector 5 5))
> (v-set! 1 17)
> (v-ref 1)

17

> (v-update! 2 sqr)
> (v++ 3)
> (v-- 4)
> (v-length)

5

> v

'#(5 17 25 6 4)

syntax

(define-mutable-hash identifier my-hash)

This will create a few extra procedures, which might be useful. These are identifier-set!, identifier-ref, identifier-ref!, identifier-update!, identifier++, identifier--, identifier-count, identifier-map and identifier-for-each and identifier-has-key?.

Examples:
> (define-mutable-hash ht (make-hash (list (cons 1 1) (cons 2 2))))
> (ht-set! 3 30)
> (procedure-arity ht-ref)

'(1 2)

> (ht-ref 3)

30

> (ht-ref 4 16)

16

> (ht-ref! 4 12)

12

> (ht-ref 4 16)

12

> (ht-update! 3 sqr)
> (ht++ 1)
> (ht-- 2)
> (ht-count)

4

> (ht-map cons)

'((1 . 2) (2 . 1) (3 . 900) (4 . 12))

> (ht-for-each (° displayln list))

(1 2)

(2 1)

(3 900)

(4 12)

> (ht-has-key? 5)

#f

> ht

'#hash((1 . 2) (2 . 1) (3 . 900) (4 . 12))

4 Showcase: Y-Combinator🔗ℹ

The fork-macro is actually similar to the S function in the SKI-calculus.

Examples:
> (define K :)
> (define I (fork () K K))
> (define P
    (fork (fork (fork ()) K) K))
> (define C
    (fork (fork (fork ()))
          (fork (K) K)
          (K I)))
> (define E
    (fork (fork (I))
          (fork () I I)))
> (I 5)

5

> (((P add1) sqr) 10)

121

> (((C add1) sqr) 10)

101

> (define Y ((C E) (P E)))

Then we can define a factorial function like that:

Examples:
> (define fac
    (fork (fork (if) zero? add1)
          (fork (fork (*) I)
                (P sub1))))
> ((Y fac) 5)

120

Alternatively, the fibonacci function:

Examples:
> (define fib
    (fork (fork (if)
                (fork (or) zero? ((C zero?) sub1))
                I)
          (fork (fork (+))
                (P sub1)
                (P ((C sub1) sub1)))))
> (map (Y fib) (range 10))

'(0 1 1 2 3 5 8 13 21 34)

5 Showcase: Easy Racket🔗ℹ

This section is inspired by an article of The alleged unreadability of J. The last section showcased some of the power of the fork, but it is barely readable. Here is a showcase of how it can also be used to make the code more readable, especially in combination with contracts.

Examples:
> (struct triangle (a b c))
> (define the id)
> (define on id)
> (define of id)
> (define squares (~ map sqr))
> (define hypotenuse triangle-c)
> (define other-two-sides (fork (list) triangle-a triangle-b))
> (define are-equal? =)
> (define calculate °)
> (define/contract triangle-is-pythagorean?
    (-> triangle? boolean?)
    (fork (are-equal?)
          (calculate the sqr on the hypotenuse)
          (calculate the sum of the squares on the other-two-sides)))
> (triangle-is-pythagorean? (triangle 3 4 5))

#t

> (triangle-is-pythagorean? (triangle 3 4 6))

#f