Pair
| Paradigm(s) | Functional |
|---|---|
| Designed by | User:Hakerh400 |
| Appeared in | 2022 |
| Computational class | Turing complete |
| Major implementations | Implementation in JavaScript |
| File extension(s) | .txt |
Pair is a functional esoteric programming language invented by User:Hakerh400 in 2022. It is designed to be very simple to understand and use, but at the same time very powerful and expressive.
Overview
Everything in this language is a value. A value can be either nil, a pair, a function, or something else:
- Nil is a unique value. It has no elements.
- Pair consists of two elements: the first and the second element.
- Function represents a computation. It takes a value as argument and returns the result.
- Something else represents something else. This is intentionally underspecified.
Call
Every value can be called. The calee is called target and the argument is called argument. Depending on what the target is, there are three cases:
- If the target is a function, substitute the formal argument with the actual argument in the function's body.
- If the target is a pair, call the argument with the first element of the pair and then call the result with the second element of the pair.
- If the target is nil, return a function that takes two arguments and returns the first or the second argument, depending on whether the argument (of the nil) is a pair or nil, respectively. If the argument (of the nil) is neither a pair nor nil, the return value is a function that takes two arguments and returns nil.
- If the target is something else, the result is undefined.
Syntax
Syntax is very simple. Source code consists of definitions. Each definition has a name, zero or more formal arguments and the function body. Example of a function definition:
f a b c = a c (b c)
This function f takes three arguments and returns the result of calling a with c and b c. Call is left associative.
Nil is represented as #. Pair is represented as (x, y). Example:
nil = # mk_pair a b = (a, b)
Constant nil is equal to nil. Function mk_pair takes two values and makes a pair of them.
Function definition may span to multiple lines, but each subsequent line must be indented by at least one space character.
Syntactic sugar
On the left we show syntactic sugar and on the right we show desugared syntax.
() ---> # a | b | c | ... ---> a (b (c (...))) (a, b, c, ...) ---> (a, (b, (c, ...))) [a, b, c, ...] ---> (a, b, c, ..., #)
The square brackets thing is called a list. Literal natural number n is represented by a list of n nils.
Character 'x' is represented by literal natural number representing its char code.
String "abc" is represented by a list of characters.
Semicolon is treated like the end of definition (it can be used to put multiple definitions in the same line).
I/O format
There must be function main in the source code. The function is called with the input string and the result is the output string.
Prelude
There are no builtin functions, so we need to define them:
-- Basic
is_nil x = # x 0 1
is_pair x = # x 1 0
-- Function
id a = a
const a b = a
flip f x y = f y x
dot f g x = f | g x
dot2 f g x y = f | g x y
fst_arg a b = a
snd_arg a b = b
comb_I = id
comb_K = const
comb_S a b c = a c | b c
comb_iota f = f comb_S comb_K
fix f = f | fix f
-- Proposition
false = 0
true = 1
ite x a b = nil x a b
ite' a b x = nil x a b
not a = ite a 0 1
to_prop = dot not not
and a b = ite a b 0
or a b = ite a 1 b
xor a b = ite a (not b) b
xnor a b = ite a b (not b)
-- Tuple
tuple_to_list n xs = ite n (tuple_to_list' (dec n) xs) []
tuple_to_list' n xs = ite n (cons (head xs) | tuple_to_list' (dec n) | tail xs) [xs]
list_to_tuple = cases_list list_to_tuple' unit
list_to_tuple' x xs = ite_null xs x | cons x | list_to_tuple xs
map_tuple = dot2 list_to_tuple map_tuple'
map_tuple' fs tp = zip_with id fs | tuple_to_list (length fs) tp
show_tuple fs tp = concat ["(", intercalate ", " (map_tuple' fs tp), ")"]
-- Unit
unit = nil
show_unit = show_tuple []
-- Pair
pair a b = (a, b)
fst p = p fst_arg
snd p = p snd_arg
curry f a b = f (a, b)
uncurry f p = f (fst p) (snd p)
map_pair f g = map_tuple [f, g]
show_pair f g = show_tuple [f, g]
map_pair' f = map_pair f f
show_pair' f = show_pair f f
pair_to_list = tuple_to_list 2
swap p = (snd p, fst p)
-- Maybe
nothing = nil
just = pair nil
is_nothing = null
is_just = not_null
from_just = snd
maybe z f x = ite x (f | from_just x) z
show_maybe f = maybe "Nothing" | dot (append "Just ") f
-- Natural number
zero = nil
succ = cons zero
inc = succ
dec = tail
iter = dot foldr const
mk_nat = foldl (dot add | mul 10) 0
show_nat n = ite n (show_nat' n) "0"
show_nat' n = ite n (snoc (show_nat' | div n 10) | add '0' | mod n 10) []
read_nat = dot mk_nat | map | flip sub '0'
odd n = mod n 2
even = dot not odd
min a b = ite (le a b) a b
max a b = ite (le a b) b a
nat_find f = nat_find' f 0
nat_find' f n = ite (f n) n | nat_find' f | succ n
nat_find1 = dot dec nat_find
suba a b = sub (max a b) (min a b)
pow' = flip pow
sqrt = root 2
between a b c = and (ge c a) (le c b)
nats = iterate inc 0
mod' = flip mod
mk_nats f = map f nats
const_nats = dot mk_nats const
dvd = dot2 not mod'
-- Optimized functions
eq* a b = ite a (ite b (and (eq (fst a) (fst b)) (eq (snd a) (snd b))) 0) (not b)
le* = dot2 null sub
lt* = dot le succ
add* = iter succ
sub* = iter dec
mul* n = iter (add n) 0
div* m n = ite (lt m n) 0 | succ | div (sub m n) n
mod* m n = ite (lt m n) m | mod (sub m n) n
pow* n = iter (mul n) 1
ge* = dot2 not lt
gt* = dot2 not le
root* k n = nat_find1 | root' k n
root' k n x = gt (pow x k) n
log* k n = nat_find1 | log' k n
log' k n x = gt (pow k x) n
-- Integer
int_of_nat = pair 0
int_neg_succ_of_nat = pair 1
sign = fst
int_val = uncurry add
show_int n = concat [ite (sign n) "-" "", show_nat (int_val n)]
mk_int sign val = (ite val sign 0, sub val sign)
same_sign a b = eq (sign a) (sign b)
neg a = mk_int (not | sign a) | int_val a
int_of_add_nat s a b = mk_int s | add a b
int_of_sub_nat s a b = mk_int (xor s | lt a b) | suba a b
add_int a b = ite (same_sign a b) int_of_add_nat int_of_sub_nat (sign a) (int_val a) (int_val b)
sub_int a b = add_int a | neg b
mul_int a b = mk_int (not | same_sign a b) | mul (int_val a) (int_val b)
int_0 = mk_int 0 0
int_1 = mk_int 0 1
int_neg_1 = mk_int 1 1
inc_int = add_int int_1
dec_int = flip sub_int int_1
is_zero_int = dot not int_val
is_neg = sign
is_pos a = and (not | sign a) (to_prop | int_val a)
is_nneg = dot not is_neg
is_npos = dot not is_pos
pow_int a b = ite (is_neg b) int_0 | mk_int (and (sign a) | odd | int_val b) | pow (int_val a) | int_val b
pow_int' = flip pow_int
abs = dot (mk_int 0) int_val
-- List
nil = []
cons = pair
head = fst
tail xs = ite_null xs nil | snd xs
null = is_nil
not_null = dot not null
ite_null = dot ite null
foldr f z xs = ite_null xs z | f (fst xs) | foldr f z | snd xs
foldl f z xs = ite_null xs z | foldl f (f z (fst xs)) | snd xs
map f = foldr (dot cons f) nil
append xs ys = foldr cons ys xs
singleton x = cons x nil
snoc xs = dot (append xs) singleton
concat = foldr append nil
index n xs = head | iter tail xs n
reverse = foldl (flip cons) []
length = foldr (const inc) 0
sum = foldr add 0
product = foldr mul 1
filter f = foldr (filter' f) []
filter' f x xs = ite (f x) (cons x xs) xs
intersperse x xs = ite xs (tail | foldr (intersperse' x) [] xs) []
intersperse' x y ys = cons x | cons y ys
intercalate x xs = concat | intersperse x | xs
repeat x = cons x | repeat x
replicate n x = take n | repeat x
take n xs = ite n (cons (head xs) (take (dec n) (tail xs))) []
drop n xs = iter tail xs n
concat_map = dot2 concat map
show_list f xs = concat ["[", intercalate ", " (map f xs), "]"]
cases_list f z xs = ite_null xs z | f (fst xs) (snd xs)
all f = foldr (dot and f) 1
any f = foldr (dot or f) 0
zip xs ys = cases_list (zip'1 ys) [] xs
zip'1 ys x xs = cases_list (zip'2 x xs) [] ys
zip'2 x xs y ys = cons (x, y) | zip xs ys
zip_with f = dot2 (map | uncurry f) zip
same_length xs ys = eq (length xs) (length ys)
tails = cases_list tails' [[]]
tails' x xs = cons (cons x xs) | tails xs
inits xs = map reverse | reverse | tails | reverse xs
range0 n = ite_null n [] | range0' | dec n
range0' = dot reverse tails
range1 = range 1
range a b = range' a | inc b
range' a b = map (add a) | range0 | sub b a
mk_list f = dot (map f) range0
mk_list' f = dot (mk_list f) inc
mk_list1 f = mk_list (dot f inc)
split_at_each f z xs = reverse | fst | foldl (split_at_each'1 f) ("", nothing) | snoc xs z
split_at_each'1 f acc c = split_at_each'2 (fst acc) c (ite | f c) | snd acc
split_at_each'2 xs c sp = maybe (xs, sp nothing (just [c])) (split_at_each'3 xs c sp)
split_at_each'3 xs c sp x = sp (cons (reverse x) xs, nothing) (xs, just (cons c x))
iterate f z = cons z | iterate f | f z
elem z = any | eq z
find_index f xs = foldr (find_index' f) nothing | zip xs nats
find_index' f x xs = ite (f | fst x) (just | snd x) xs
elem_index z = find_index (eq z)
elem_index' z xs = from_just | elem_index z xs
split_at n xs = ite (or (null n) (null xs)) ([], xs) | uncurry (split_at' | dec n) xs
split_at' n x xs = map_pair (cons x) id | split_at n xs
replace n x xs = uncurry append | map_pair id (dot (cons x) tail) | split_at n xs
-- Vector
vec_const = replicate
vec_zero n = vec_const n 0
show_vec = show_list show_nat
dist_norm k v1 v2 = root k | sum | zip_with (dist_norm' k) v1 v2
dist_norm' k x y = suba (pow x k) (pow y k)
dist = dist_norm 2
distm = dist_norm 1
hypot v = dist v | vec_zero | length v
-- Matrix
mk_mat f w h = mk_list (mk_mat' (flip f) w) h
mk_mat' f w y = mk_list (f y) w
show_mat = dot show_list show_list
map_mat = dot map map
foldl_mat f z m = foldl f z | concat m
foldr_mat f z m = foldr f z | concat m
-- Character
chr = id
ord = id
show_char c = ['\, c, '\]
is_space = eq ' '
is_new_line c = or (eq c '\r') (eq c '\n')
is_lower_letter = between 'a' 'z'
is_upper_letter = between 'A' 'Z'
is_letter c = or (is_lower_letter c) (is_upper_letter c)
to_lower_char c = ite (is_upper_letter c) (add c 32) c
to_upper_char c = ite (is_lower_letter c) (sub c 32) c
-- String
words = split_at_each is_space ' '
lines = split_at_each is_new_line '\n'
unwords = intercalate " "
unlines = intercalate "\n"
show_str xs = concat ["\"", xs, "\""]
to_lower = map to_lower_char
to_upper = map to_upper_char
capitalize = cases_list (dot cons to_upper_char) ""
split_at_char c = split_at_each (eq c) c
-- Monad
map_m f = cases_list (map_m'1 f) | pure []
map_m'1 f x xs = do
y <- f x
ys <- map_m f xs
return cons y ys
-- State
pure = pair
bind m f s = uncurry f | m s
seq m m1 = bind m | const m1
run_state = id
eval_state = dot2 fst run_state
exec_state = dot2 snd run_state
get s = (s, s)
put s1 s = (unit, s1)
gets = dot (bind get) | dot pure
modify = dot (bind get) | dot put
get' = dot gets index
put' = dot2 modify replace
modify' i = dot (bind | get' i) | dot | put' i
gets' i = dot (bind | get' i) | dot pure
Monads
The language supports special syntax for monads. We introduce additional syntactic sugar (\n represents new line):
f args = do \n x = expr \n g ---> f args = f' args expr \n f' args x = do \n g f args = do \n x <- expr \n g ---> f args = bind expr | f' args \n f' args x = do \n g f args = do \n expr \n g ---> f args = seq expr | f' args \n f' args = do \n g f args = do \n g ---> f args = g return ---> pure |
The prelude supports only the state monad. Here is an example:
main n = show_list show_nat | exec_state (func | read_nat n) [1, 2, 3, 4] func n = do xs <- get ys = cons n xs put ys return ()
This program reads a natural number n and outputs the list [n, 1, 2, 3, 4]
Examples
Cat program
main = id
Hello, World!
main = const "Hello, World!"
Reverse the input
main = reverse
Sum odd numbers
Read a list of space-separated natural numbers in base 10 and output the sum of odd numbers from the list.
main inp = show_nat | sum | filter odd | map read_nat | words inp
Quine
Modulo the prelude.
a="main=const|concat[[97,61],show_str a,[10],a]" main=const|concat[[97,61],show_str a,[10],a]
Truth-machine
main inp = ite (eq inp "0") "0" | repeat '1'
N-th digit of pi
main inp = show_nat | func | read_nat inp func i = func'1 i | pow 10 | succ i func'1 i w = mod (div (mul 4 | mul (pow 10 i) | foldr_mat (func'2 w) 0 | mk_mat pair w w) | pow w 2) 10 func'2 w p c = add c | lt (hypot | pair_to_list p) w
Fibonacci sequence
Output the first n Fibonacci numbers.
main n = show_list show_nat | mk_list fib | read_nat n fib n = ite n (fib' | dec n) 0 fib' n = ite n (add (fib n) (fib | dec n)) 1
99 bottles of beer
main = const | intercalate "\n\n" | reverse | mk_list func 100 func n = concat [capitalize (bottles_of_beer_wall n), ", ", bottles_of_beer n, ".\n", ite n (take_one_down n) go_to_the_store] bottles_of_beer n = concat [ite n (show_nat n) "no more", " bottle", ite (eq n 1) "" "s", " of beer"] bottles_of_beer_wall n = concat [bottles_of_beer n, " on the wall"] take_one_down n = concat ["Take one down and pass it around, ", bottles_of_beer_wall (dec n), "."] go_to_the_store = concat ["Go to the store and buy some more, ", bottles_of_beer_wall 99, "."]
Factorial
main n = show_list show_nat | mk_list' (dot product range1) | read_nat n
Prime numbers
Output the first n prime numbers.
main n = show_list show_nat | take (read_nat n) | filter prime nats prime n = and (ge n 2) | all (mod n) | range 2 | dec n
Disan count
main n = show_list show_nat | filter even | range0 | read_nat n
Digital root calculator
main n = show_nat | func | read_nat n func n = ite (lt n 10) n | func | sum | digits n digits n = ite (lt n 10) [n] | cons (mod n 10) | digits | div n 10
Brainfuck interpreter
Input string consists of the brainfuck source code and the brainfuck input, separated by a semicolon.
main src = uncurry run | map_pair parse id | list_to_tuple | split_at_char ';' src inc_val = add_val 1 move_left = move 3 4 move_right = move 4 3 advance = modify tail dec_val = add_val 255 basic_insts = "<>+-,." get_val = gets' 4 head pure_inst i = pure (i, nil) exec_insts = map_m exec_inst parse src = eval_state parse_insts src eof = bind get | ite' eof' | pure true set_val val = modify' 4 | replace 0 val output = bind get_val | dot (modify' 2) cons parse_loop = bind parse_insts | dot pure | pair 6 parse_insts = bind eof | ite' (pure []) parse_insts' exec_inst inst = index (fst inst) inst_funcs | snd inst add_val n = bind get_val | dot (dot set_val | mod' 256) | add n run insts inp = eval_state run' [insts, inp, "", const_nats 0, const_nats 0] run' = bind (get' 0) | dot (flip seq | bind (get' 2) | dot pure reverse) | exec_insts exec_loop insts = bind get_val | ite' (seq (exec_insts insts) | exec_loop insts) | pure () eof' = bind (parse_char 0) | dot (ite' (seq advance | pure true) | pure false) | eq ']' inst_funcs = snoc (map const [move_left, move_right, inc_val, dec_val, input, output]) exec_loop parse_inst = bind (parse_char 1) | dot (maybe parse_loop pure_inst) | flip elem_index basic_insts parse_insts' = do inst <- parse_inst insts <- parse_insts return cons inst insts parse_char adv = do src <- get ite adv advance | pure () return head src move a b = do mem <- get' a put' a | tail mem modify' b | cons | head mem input = do inp <- get' 1 put' 1 | tail inp set_val | cases_list fst_arg 0 inp
Deadfish interpreter
output_as_string = false main src = format_output | reverse | index 1 | exec_state (map_m run src) [int_0, ""] format_output = ite output_as_string (map | dot int_val abs) | show_list show_int run x = index (elem_index' x "idso") inst_funcs inst_funcs = [increment, decrement, square, output] normalize n = ite (elem n [int_neg_1, mk_int 0 256]) int_0 n modify_val f = modify' 0 | dot normalize f increment = modify_val inc_int decrement = modify_val dec_int square = modify_val | pow_int' | mk_int 0 2 output = bind (get' 0) | dot (modify' 1) cons
FizzBuzz
main n = unlines | mk_list1 func | read_nat n func n = func' n (dvd 3 n) (dvd 5 n) func' n m3 m5 = ite (or m3 m5) (concat [ite m3 "Fizz" "", ite m5 "Buzz" ""]) | show_nat n