The combinator birds
Generated by tools/gen_example_pages.py from mnd_scripts/examples/example_lambda_birds.mnd — the output below is a real run, not a transcription. Regenerating the page reruns it.
bird-combinator lambda notation, and logical lambda expressions built from Church booleans.
The program#
# ============================================================================
# example_lambda_birds.mnd
# ----------------------------------------------------------------------------
# FUNCTIONALITY: bird-combinator lambda notation, and logical lambda
# expressions built from Church booleans.
#
# Maned's functional core is combinatory logic under Smullyan's bird names
# (identity, kestrel, kite, bluebird, cardinal, warbler, ...). What makes the
# language unusual is that in a POSTFIX language you do not need to *call* the
# combinators -- the stack operators ARE them. Combinatory logic and RPN are
# the same idea written twice:
#
# BIRD LAMBDA TERM MANED POSTFIX MEANING
# identity I \x. x x the value itself
# kestrel K \x.\y. x x y drop keep the first
# kite KI \x.\y. y x y swap drop keep the second
# warbler W \f.\x. f x x x dup f feed x to f twice
# cardinal C \f.\x.\y. f y x x y swap f flip the arguments
# bluebird B \f.\g.\x. f (g x) x g f composition
# thrush T \x.\f. f x x f postfix itself
#
# `x g f` needs no combinator at all: juxtaposition in postfix IS composition.
# That is why B, T and I have no runtime cost here -- they are the notation.
#
# CHURCH BOOLEANS. In lambda calculus `true` is K and `false` is KI, and
# `if c then x else y` is just `c x y`. Maned is integer-quantized, so a
# predicate yields a 0/1 mask instead of a function, and selection becomes
# branch-free arithmetic that is identical on the CPU interpreter and on an
# FPGA worker:
#
# select(c, x, y) = c*x + (1-c)*y # c is 1 (K) or 0 (KI)
#
# Every line below RUNS. The reserved words `identity`, `kestrel`, `kite`,
# `mockingbird`, `bluebird`, `cardinal`, `starling`, `thrush`, `warbler`,
# `owl`, `compose`, `blackbird`, `psi`, `phoenix`, `vireo`, `ycombinator` are
# held by the grammar for the named-combinator runtime; the CPU lambda
# evaluator (src/interp/lambda.cpp) implements I, K, KI and compose as terms,
# but they are NOT yet reachable as operators from a flow body. Use the
# postfix spellings above -- they are the same functions.
#
# RUN: ../maned-run examples/example_lambda_birds.mnd
# ============================================================================
mnd::quantmax=1000;
mnd::quantmin=-1000;
mnd::quantres=0;
in::x = [[10,20],[30,40]];
in::y = [[99, 1],[ 7,77]];
calc::lambda_flow birds(x, y) {
# --- I : identity ------------------------------------------------------
# \x. x -- an expression that is just an operand binds that operand.
x same =
# --- K : kestrel, the constant function (and Church TRUE) --------------
# \x.\y. x -- push both, discard the second.
x y drop kestrel_keeps_x =
# --- KI : kite, K applied to identity (and Church FALSE) --------------
# \x.\y. y -- push both, swap, discard.
x y swap drop kite_keeps_y =
# --- W : warbler, the duplicator --------------------------------------
# \f.\x. f x x -- `dup` supplies the same argument twice.
x dup elemwise_add warbler_doubles =
x dup matmul warbler_squares =
# --- C : cardinal, argument flip --------------------------------------
# \f.\x.\y. f y x -- `swap` before a non-commutative operator.
x y elemwise_sub cardinal_before =
x y swap elemwise_sub cardinal_after =
# --- B : bluebird, composition ----------------------------------------
# \f.\g.\x. f (g x) -- write g then f. No combinator token required.
x transpose relu bluebird_relu_of_transpose =
# --- Church-boolean conditional: `c x y` ------------------------------
# The predicate is the lambda; the mask is its quantized reification.
x y > c = # c = 1 where x > y, else 0 (K vs. KI)
c x elemwise_mul k_branch = # the TRUE branch, gated by c
1 c sub not_c = # logical negation, by arithmetic
not_c y elemwise_mul ki_branch =# the FALSE branch, gated by (1-c)
k_branch ki_branch elemwise_add selected = # elementwise max(x, y)
# The same shape with the branches exchanged is elementwise min(x, y),
# which is the whole point: swapping K and KI swaps the branches.
c y elemwise_mul k_swapped =
not_c x elemwise_mul ki_swapped =
k_swapped ki_swapped elemwise_add selected_other =
# --- a two-level logical lambda ---------------------------------------
# (x > y) AND (x > 15) -- conjunction of two predicates, then one select.
x 15 > big =
c big and both =
both x elemwise_mul kept =
1 both sub not_both =
not_both 0 elemwise_mul zeroed =
kept zeroed elemwise_add big_and_larger =
} return same, kestrel_keeps_x, kite_keeps_y,
warbler_doubles, warbler_squares,
cardinal_before, cardinal_after,
bluebird_relu_of_transpose,
c, selected, selected_other, big, both, big_and_larger;
The run#
$ maned-run mnd_scripts/examples/example_lambda_birds.mnd
flow 'birds' outputs:
same : [2x2] = [10, 20, 30, 40]
kestrel_keeps_x : [2x2] = [10, 20, 30, 40]
kite_keeps_y : [2x2] = [99, 1, 7, 77]
warbler_doubles : [2x2] = [20, 40, 60, 80]
warbler_squares : [2x2] = [700, 1000, 1500, 2200]
cardinal_before : [2x2] = [-89, 19, 23, -37]
cardinal_after : [2x2] = [89, -19, -23, 37]
bluebird_relu_of_transpose : [2x2] = [10, 30, 20, 40]
c : [2x2] = [0, 1, 1, 0]
selected : [2x2] = [99, 20, 30, 77]
selected_other : [2x2] = [10, 1, 7, 40]
big : [2x2] = [0, 1, 1, 1]
both : [2x2] = [0, 1, 1, 0]
big_and_larger : [2x2] = [0, 20, 30, 0]
Next#
- Language guide — every operation the examples use, in the full table.
- RPN syntax specification — the normative rules.