The aviary: combinators in X_eTaL
Raymond Smullyan's birds as a library, run as you read

Table of Contents

A combinator is a function that only rearranges, repeats or drops its arguments. Raymond Smullyan's To Mock a Mockingbird names them as birds, and the standard library Combinators (lib/Combinators.xtl) holds every one of his birds that type-checks, under its letter: docs/birds.md lists them all, with their definitions and types. Every block below is run by xetal through ob-xetal and its result recorded under it.

Importing the library

A program imports a library under an alias of its choosing; the library's names are then written with that prefix:

import-annotated.svg

ᶜ⁼u̲se< "Combinators"
1 ᶜK̲ 2

⍝ typed:
⍝   "c:" u_se< "Combinators"
⍝   1 c:K_ 2
1

K, the Kestrel, keeps its first argument and drops the second.

Seeing the order of arguments

To watch a bird move its arguments around, give it functions whose results spell their arguments: u:a_b 1 2 is 12, u:a_bc 1 2 3 is

  1. A quoted function before a bird is its first argument (the

nearest first), then comes a value on the left, then one on the right; (e)_ v applies a function that is computed.

ᵘa̲b ← { a b → (10 × a) + b }
ᵘa̲bc ← { a b c → (100 × a) + (10 × b) + c }
ᵘi̲nc ← { ⍵ + 1 }
ᵘd̲ouble ← { ⍵ × 2 }
1 'ᵘa̲b ᶜC̲ 2

⍝ typed:
⍝   u:a_b := { a b -> (10 * a) + b }
⍝   u:a_bc := { a b c -> (100 * a) + (10 * b) + c }
⍝   u:i_nc := { _r + 1 }
⍝   u:d_ouble := { _r * 2 }
⍝   1 'u:a_b c:C_ 2
21

C, the Cardinal, swaps them: C x y z is x z y, so C a_b 1 2 is a_b 2 1. Written between two values, a function takes the left one, then the right one:

swap-annotated.svg

Composing

B, the Bluebird, composes: B x y z is x (y z). Here x is inc (the nearest operand) and y is double:

compose-annotated.svg

'ᵘd̲ouble 'ᵘi̲nc ᶜB̲ 5

⍝ typed:
⍝   'u:d_ouble 'u:i_nc c:B_ 5
11

The atop train [u:i_nc u:d_ouble] 5 is the same composition written in place; trains.org sets each train beside its bird.

The Blackbird B1 composes with a function of two arguments: B1 x y z w is x (y z w), inc applied to a_b 1 2:

1 'ᵘa̲b 'ᵘi̲nc ᶜB̲1 2

⍝ typed:
⍝   1 'u:a_b 'u:i_nc c:B_1 2
13

Using a value twice

W, the Warbler, gives its function one value on both sides; S, the Starling, applies x to z and to y applied to z: S x y z is x z (y z), here a_b 3 (double 3):

starling-annotated.svg

'ᵘa̲b ᶜW̲ 3
'ᵘd̲ouble 'ᵘa̲b ᶜS̲ 3

⍝ typed:
⍝   'u:a_b c:W_ 3
⍝   'u:d_ouble 'u:a_b c:S_ 3
33
36

Holding values

T, the Thrush, applies its second argument to its first, and V, the Vireo, holds two values until a function arrives:

3 ᶜT̲ 'ᵘd̲ouble
(3 ᶜV̲ 4)_ '+
(3 ᶜV̲ 4)_ 'ᵘa̲b

⍝ typed:
⍝   3 c:T_ 'u:d_ouble
⍝   (3 c:V_ 4)_ '+
⍝   (3 c:V_ 4)_ 'u:a_b
6
7
34

Recursion

Y, the Sage Bird, makes recursion from a function that is handed itself: Y f is f (Y f). It does not unfold forever because the function's self parameter is lazy (~), used only when needed:

sage-annotated.svg

ᵘf̲act ← { ~s̲elf n → n ≤ 1 ? 1◆ n × s̲elf n − 1 }
'ᵘf̲act ᶜY̲ 10

⍝ typed:
⍝   u:f_act := { ~s_elf n -> n <= 1 ? 1; n * s_elf n - 1 }
⍝   'u:f_act c:Y_ 10
3628800

A superscript on a function repeats it, function power, which is how Life steps its boards: u:l_ife^4.

ᵘd̲ouble¹⁰ 1

⍝ typed:
⍝   u:d_ouble^10 1
1024

The birds with no type

The Mockingbird M applies its argument to itself, M x is x x, and no finite type describes that; nor the Lark, the Double Mockingbird, Turing's bird or the textbook Y built from them. They run when the checker is skipped, here outside the session with --untyped: M given the identity gives it back, and the textbook Y computes a factorial too.

ᵘM̲ ← { x̲ → x̲ 'x̲ }
ᵘI̲ ← { x → x }
('ᵘI̲ ᵘM̲)_ 42
ᵘY̲ ← { f̲ → { x̲ → f̲ x̲ 'x̲ } '{ x̲ → f̲ x̲ 'x̲ } }
ᵘF̲ ← { ~s̲elf n → n ≤ 1 ? 1◆ n × s̲elf n − 1 }
(ᵘY̲ 'ᵘF̲)_ 5

⍝ typed:
⍝   u:M_ := { x_ -> x_ 'x_ }
⍝   u:I_ := { x -> x }
⍝   ('u:I_ u:M_)_ 42
⍝   u:Y_ := { f_ -> { x_ -> f_ x_ 'x_ } '{ x_ -> f_ x_ 'x_ } }
⍝   u:F_ := { ~s_elf n -> n <= 1 ? 1; n * s_elf n - 1 }
⍝   (u:Y_ 'u:F_)_ 5
42
120

Literate documents · Live demo · Repository