Trains and birds
two ways to compose functions without naming the argument
Table of Contents
A train in square brackets composes functions without naming the
argument, as in APL and J: [F G H] x is (F x) G (H x), and
[F G] x is F (G x). The Combinators library names the same
compositions as Raymond Smullyan's birds (docs/literate/birds.org):
the Bluebird composes, the Starling uses a value twice. This document
writes each train next to its bird and checks that the two agree.
The short answer to "trains or birds?": a train is syntax, written where it is used, whose elements are any functions and whose arity comes from where it is written; a bird is an ordinary function from a library, which takes the functions it composes as quoted operands and can itself be passed around or partly applied. Both desugar to plain application, and both give the same results.
The birds and some functions to watch them with
The library is imported under an alias, and a few small functions show
which argument went where: u:a_b 1 2 is 12.
ᶜ⁼u̲se< "Combinators" ᵘa̲b ← { a b → (10 × a) + b } ᵘi̲nc ← { ⍵ + 1 } ᵘd̲ouble ← { ⍵ × 2 } ᵘs̲quare ← { ⍵ × ⍵ } ⍝ typed: ⍝ "c:" u_se< "Combinators" ⍝ u:a_b := { a b -> (10 * a) + b } ⍝ u:i_nc := { _r + 1 } ⍝ u:d_ouble := { _r * 2 } ⍝ u:s_quare := { _r * _r }
Atop and the Bluebird
Two functions in a train are an atop: the right one applies first, the
left one to its result. That is the Bluebird, B x y z = x (y z). A
bird takes its functions as operands, the nearest first, so the
function applied first is written first.
[ᵘi̲nc ᵘd̲ouble] 5 'ᵘd̲ouble 'ᵘi̲nc ᶜB̲ 5 ⍝ typed: ⍝ [u:i_nc u:d_ouble] 5 ⍝ 'u:d_ouble 'u:i_nc c:B_ 5
11 11
The two agree on arrays too: m_atch compares whole arrays.
([ᵘi̲nc ᵘd̲ouble] 1 2 3) m̲atch 'ᵘd̲ouble 'ᵘi̲nc ᶜB̲ 1 2 3 ⍝ typed: ⍝ ([u:i_nc u:d_ouble] 1 2 3) m_atch 'u:d_ouble 'u:i_nc c:B_ 1 2 3
1
Written between two values, the atop's right function takes both, and
the left one its result: that is the Blackbird, B1 x y z w = x (y z
w).
1 [ᵘi̲nc ᵘa̲b] 2 1 'ᵘa̲b 'ᵘi̲nc ᶜB̲1 2 ⍝ typed: ⍝ 1 [u:i_nc u:a_b] 2 ⍝ 1 'u:a_b 'u:i_nc c:B_1 2
13 13
The hook and the Starling
i_d x is just x, so in a fork [i_d F G] x is (i_d x) F (G
x), which is x F (G x): J's hook. For example [i_d - n_eg] 5 is
5 - (n_eg 5), which is 10. That is the Starling, S x y z
= x z (y z).
[i̲d ᵘa̲b ᵘd̲ouble] 3 'ᵘd̲ouble 'ᵘa̲b ᶜS̲ 3 ⍝ typed: ⍝ [i_d u:a_b u:d_ouble] 3 ⍝ 'u:d_ouble 'u:a_b c:S_ 3
36 36
With i_d on both sides a train gives one value to a function on both
sides, as the Warbler does.
[i̲d ᵘa̲b i̲d] 3 'ᵘa̲b ᶜW̲ 3 ⍝ typed: ⍝ [i_d u:a_b i_d] 3 ⍝ 'u:a_b c:W_ 3
33 33
The fork and the Phoenix
Three functions are a fork: the outer two apply to the argument and the middle one joins their results. Its bird, Curry's Phoenix (S'), is not in Smullyan's book, so the library leaves it out; it is one line.
ᵘP̲hi ← { c̲ f̲ g̲ x → (f̲ x) c̲ g̲ x } [n̲eg + ᵘs̲quare] 1 2 3 'ᵘs̲quare 'n̲eg '+ ᵘP̲hi 1 2 3 ⍝ typed: ⍝ u:P_hi := { c_ f_ g_ x -> (f_ x) c_ g_ x } ⍝ [n_eg + u:s_quare] 1 2 3 ⍝ 'u:s_quare 'n_eg '+ u:P_hi 1 2 3
0 2 6 0 2 6
The elements of a train can be any function, a derived one such as a reduction included. The mean is the sum divided by the count:
['+ r̲/ ÷ t̲ally] 1 2 3 4 ⍝ typed: ⍝ ['+ r_/ / t_ally] 1 2 3 4
2.5
The tacks and the simplest birds
The identity and the tacks are the Idiot bird, the Kestrel and the
Kite: i_d x is x, x l_eft y is x, x r_ight y is y.
(ᶜI̲ 7) = i̲d 7 (1 ᶜK̲ 2) = 1 l̲eft 2 ⍝ typed: ⍝ (c:I_ 7) = i_d 7 ⍝ (1 c:K_ 2) = 1 l_eft 2
1 1
Where they differ
A bird is a value. Given fewer arguments than it takes, it waits for the rest, so a composition can be named by partial application:
ᵘt̲wice ← 'ᵘd̲ouble 'ᵘd̲ouble ᶜB̲ ᵘt̲wice 5 ⍝ typed: ⍝ u:t_wice := 'u:d_ouble 'u:d_ouble c:B_ ⍝ u:t_wice 5
20
A train can be named too (u:a_vg := ['+ r_/ / t_ally]), and quoted
as an operand, here to e_ach and to a bird:
'[t̲ally d̲isclose] e̲ach "ab" "cde" "f" '[ᵘi̲nc ᵘd̲ouble] 'n̲eg ᶜB̲ 5 ⍝ typed: ⍝ '[t_ally d_isclose] e_ach "ab" "cde" "f" ⍝ '[u:i_nc u:d_ouble] 'n_eg c:B_ 5
2 3 1 -11
A train is written in place and its arity comes from where it stands:
[F G H] x is monadic and x [F G H] y dyadic. A named train is
monadic, so a named dyadic composition is a lambda, or a bird such as
the Blackbird. The birds also reach where trains do not: the Cardinal
swaps arguments, the Vireo holds two values, the Sage bird recurses.
Reading a train, and its errors
Three functions are always a fork, so a chain of three is written with
an atop inside: [r_ev [n_eg a_bs]], not [r_ev n_eg a_bs]. When a
train is wrong the error points at the element at fault and says what
the train means there:
[r_ev n_eg a_bs] -1 2 -3 error[infinite-type]: a type would contain itself: a -> b at 6..10 note: in the train `[r_ev n_eg a_bs]`, `n_eg` is applied as `(r_ev x) n_eg (a_bs x)`, where x is the train's argument note: `n_eg` takes one argument, but here it is given two
[r̲ev [n̲eg a̲bs]] -1 2 -3 ⍝ typed: ⍝ [r_ev [n_eg a_bs]] -1 2 -3
-3 -2 -1
Types decide the rest. t_ally always gives an Int, and / takes two
numbers of one type, so the mean above takes Ints only; for Floats,
both sides become Floats first, in trains of their own:
[[f̲loat '+ r̲/] ÷ [f̲loat t̲ally]] 1.5 2.5 ⍝ typed: ⍝ [[f_loat '+ r_/] / [f_loat t_ally]] 1.5 2.5
2.0
Side by side
| Train | Means | Bird |
|---|---|---|
[F G] x |
F (G x) |
Bluebird: 'G 'F c:B_ x |
x [F G] y |
F (x G y) |
Blackbird: x 'G 'F c:B_1 y |
[i_d F G] x |
x F (G x) |
Starling: 'G 'F c:S_ x |
[i_d F i_d] x |
x F x |
Warbler: 'F c:W_ x |
[F G H] x |
(F x) G (H x) |
Phoenix: 'H 'F 'G u:P_hi x |
i_d, l_eft, r_ight |
x; x of x y; y of x y | Idiot, Kestrel, Kite |