List/Church.bruijn
# MIT License, Copyright (c) 2022 Marvin Borner
# Lists in Church/Boehm-Berarducci encoding using pairs
# implementations are generally lazy (except when they're broken)
:import std/Combinator .
:import std/Pair P
:import std/Logic .
:import std/Number/Ternary .
:import std/Box B
# empty list element
empty false ⧗ (List a)
# returns true if a list is empty
# can generally be omitted: ∅?c foo bar = c [[[bar]]] foo
empty? [0 [[[false]]] true] ⧗ (List a) → Boolean
∅?‣ empty?
:test (∅?empty) (true)
# creates list with single element
singleton [P.pair 0 empty] ⧗ a → (List a)
{}‣ singleton
# prepends an element to a list
cons P.pair ⧗ a → (List a) → (List a)
…:… cons
:test ((+1) : {}(+2)) (P.pair (+1) (P.pair (+2) empty))
:test (∅?({}(+2))) (false)
# returns the head of a list or empty
head P.fst ⧗ (List a) → a
^‣ head
:test (^((+1) : {}(+2))) ((+1))
# returns the tail of a list or empty
tail P.snd ⧗ (List a) → (List a)
~‣ tail
:test (~((+1) : {}(+2))) ({}(+2))
# returns the length of a list in balanced ternary
length z [[[rec]]] (+0) ⧗ (List a) → Number
rec 0 [[[case-inc]]] case-end
case-inc 5 ++4 1
case-end 1
∀‣ length
:test (∀((+1) : {}(+2))) ((+2))
:test (∀empty) ((+0))
# applies each element of a list to a function (left-associative)
apply z [[[rec]]] ⧗ (a* → b) → (List a) → b
rec 0 [[[case-app]]] case-end
case-app 5 (4 2) 1
case-end 1
…<!… apply
:test (…+… <! ((+1) : {}(+2))) ((+3))
# applies each element of the tail to the head (left-associative)
eval &apply ⧗ (Pair (a → b) (List a)) → b
!‣ eval
:test (!(…+… : ((+1) : {}(+2)))) ((+3))
# applies each element of the tail to the head (right-associative)
eval-r z [[rec]]
rec 0 [[[case-app]]] case-end
case-app ∅?1 2 (2 (4 1))
case-end 1
~!‣ eval-r
:test (~!(inc : (inc : {}(+1)))) ((+3))
# returns the element at unary index in list
index-unary [[P.fst (0 P.snd 1)]] ⧗ (List a) → Unary → a
# returns the element at index in list
index z [[[rec]]] ⧗ (List a) → Number → a
rec 1 [[[case-index]]] case-end
case-index =?3 2 (5 1 --3)
case-end empty
…!!… index
:test (((+1) : ((+2) : {}(+3))) !! (+0)) ((+1))
:test (((+1) : ((+2) : {}(+3))) !! (+2)) ((+3))
:test (((+1) : ((+2) : {}(+3))) !! (-1)) (empty)
:test (((+1) : ((+2) : {}(+3))) !! (+3)) (empty)
# constructs a list of successively reduced values
scanl z [[[[1 : rec]]]] ⧗ (b → a → b) → b → (List a) → (List b)
rec 0 [[[case-scan]]] case-end
case-scan 6 5 (5 4 2) 1
case-end 0
:test (scanl …+… (+0) ((+1) : ((+2) : {}(+3)))) ((+0) : ((+1) : ((+3) : {}(+6))))
# applies a left fold on a list
foldl z [[[[rec]]]] ⧗ (b → a → b) → b → (List a) → b
rec 0 [[[case-fold]]] case-end
case-fold 6 5 (5 4 2) 1
case-end 1
:test ((foldl …+… (+0) ((+1) : ((+2) : {}(+3)))) =? (+6)) (true)
:test ((foldl …-… (+6) ((+1) : ((+2) : {}(+3)))) =? (+0)) (true)
# foldl without starting value
foldl1 [[foldl 1 ^0 ~0]] ⧗ (a → a → a) → (List a) → a
# applies a right fold on a list
foldr [[[z [[rec]] 0]]] ⧗ (a → b → b) → b → (List b) → b
rec 0 [[[case-fold]]] case-end
case-fold 7 2 (4 1)
case-end 3
:test ((foldr …+… (+0) ((+1) : ((+2) : {}(+3)))) =? (+6)) (true)
:test ((foldr …-… (+2) ((+1) : ((+2) : {}(+3)))) =? (+0)) (true)
# foldr without starting value
foldr1 [[foldr 1 ^0 ~0]] ⧗ (a → a → a) → (List a) → a
# applies or to all list elements
lor? foldr or? false ⧗ (List Boolean) → Boolean
# largest element by compare function
max-by [foldl1 max'] ⧗ (a → a → Number) → (List a) → a
max' [[>?(2 1 0) 1 0]]
:test (max-by (compare ⋔ length) ("abc" : ("ab" : {}"abcd"))) ("abcd")
# smallest element by compare function
min-by [foldl1 min'] ⧗ (a → a → Number) → (List a) → a
min' [[<?(2 1 0) 1 0]]
:test (min-by (compare ⋔ length) ("abc" : ("ab" : {}"abcd"))) ("ab")
⋁?‣ lor?
:test (⋁?(true : {}true)) (true)
:test (⋁?(true : {}false)) (true)
:test (⋁?(false : {}false)) (false)
# applies and to all list elements
land? foldr and? true ⧗ (List Boolean) → Boolean
⋀?‣ land?
:test (⋀?(true : {}true)) (true)
:test (⋀?(true : {}false)) (false)
:test (⋀?(false : {}false)) (false)
# reverses a list
reverse foldl \cons empty ⧗ (List a) → (List a)
<~>‣ reverse
:test (<~>((+1) : ((+2) : {}(+3)))) ((+3) : ((+2) : {}(+1)))
# appends two lists (Tromp)
append &(z [[[[[0 3 (2 4 1)]]]]]) ⧗ (List a) → (List a) → (List a)
…++… append
:test ({}(+1) ++ empty) ({}(+1))
:test (empty ++ {}(+1)) ({}(+1))
:test (empty ++ empty) (empty)
:test (((+1) : ((+2) : {}(+3))) ++ {}(+4)) ((+1) : ((+2) : ((+3) : {}(+4))))
# appends an element to a list
snoc [[1 ++ {}0]] ⧗ (List a) → a → (List a)
…;… snoc
:test (empty ; (+1)) ({}(+1))
:test ({}(+1) ; (+2)) ((+1) : {}(+2))
# generates a variadic box-based function from a list-based function
# TODO: Find a solution that does not require boxed terms
variadic [[y [[[[rec]]]] 1 0 [[0]]]] ⧗ ((List a) → b) → ((Box a)* → (Box c) → b)
rec B.∅?1 (2 0) [4 3 0 (1 ; (B.get i 2))]
:test (variadic reverse B.<>(+1) B.<>(+2) B.<>(+3) B.empty) ((+3) : ((+2) : {}(+1)))
# constructs list out of boxed terms, ended with an empty box
list variadic i ⧗ (Box a)* → (Box b) → (List a)
:test (list B.<>(+2) B.<>(+3) B.empty) ((+2) : {}(+3))
:test (^(list B.<>(+2) B.<>(+3))) ((+2) : {}(+3))
# maps each element to a function
map z [[[rec]]] ⧗ (a → b) → (List a) → (List b)
rec 0 [[[case-map]]] case-end
case-map (4 2) : (5 4 1)
case-end empty
…<$>… map
:test (++‣ <$> ((+1) : ((+2) : {}(+3)))) ((+2) : ((+3) : {}(+4)))
# filters a list based on a predicate
filter z [[[rec]]] ⧗ (a → Boolean) → (List a) → (List a)
rec 0 [[[case-filter]]] case-end
case-filter 4 2 (cons 2) i (5 4 1)
case-end empty
…<#>… filter
:test (zero? <#> ((+1) : ((+0) : {}(+3)))) ({}(+0))
# returns the last element of a list
last ^‣ ∘ <~>‣ ⧗ (List a) → a
_‣ last
:test (_((+1) : ((+2) : {}(+3)))) ((+3))
# returns everything but the last element of a list
init z [[rec]] ⧗ (List a) → (List a)
rec 0 [[[case-init]]] case-end
case-init 1 [[[5 : (7 4)]]] empty
case-end empty
:test (init ((+1) : ((+2) : {}(+3)))) ((+1) : {}(+2))
# concatenates a list of lists to one list
concat foldr append empty ⧗ (List a) → (List a) → (List a)
:test (concat (((+1) : {}(+2)) : {}((+3) : {}(+4)))) ((+1) : ((+2) : ((+3) : {}(+4))))
:test (concat ("a" : {}"b")) ("ab")
# maps a function returning list of list and concatenates
concat-map concat ∘∘ map ⧗ (a → (List b)) → (List a) → (List b)
…<++>… concat-map
:test ([-0 : {}0] <++> ((+1) : {}(+2))) ((-1) : ((+1) : ((-2) : {}(+2))))
# zips two lists discarding excess elements
zip z [[[rec]]] ⧗ (List a) → (List b) → (List (Pair a b))
rec (∅?1 ⋁? ∅?0) case-end case-zip
case-zip (^1 : ^0) : (2 ~1 ~0)
case-end empty
:test (zip ((+1) : {}(+2)) ((+2) : {}(+1))) (((+1) : (+2)) : {}((+2) : (+1)))
# zips three lists discarding excess elements
# TODO: add/use triple type (list element types can be different)
zip3 z [[[[rec]]]] ⧗ (List a) → (List a) → (List a) → (List (List a))
rec (∅?2 ⋁? ∅?1 ⋁? ∅?0) case-end case-zip
case-zip (^2 : (^1 : {}(^0))) : (3 ~2 ~1 ~0)
case-end empty
:test (zip3 ((+1) : {}(+2)) ((+2) : {}(+1)) ((+3) : {}(+0))) (((+1) : ((+2) : {}(+3))) : {}((+2) : ((+1) : {}(+0))))
# applies pairs of the zipped list as arguments to a function
zip-with z [[[[rec]]]] ⧗ (a → b → c) → (List a) → (List b) → (List c)
rec 1 [[[case-zip]]] case-end
case-zip 3 [[[(8 5 2) : (9 8 4 1)]]] empty
case-end empty
:test (zip-with …+… ((+1) : {}(+2)) ((+2) : {}(+1))) ((+3) : {}(+3))
# list comprehension
{…|…} map ⧗ (a → b) → (List a) → (List b)
:test ({ ++‣ | ((+0) : {}(+2)) }) ((+1) : {}(+3))
# doubled list comprehension
{…|…;…} zip-with ⧗ (a → b → c) → (List a) → (List b) → (List c)
:test ({ …+… | ((+0) : {}(+2)) ; ((+1) : {}(+3)) }) ((+1) : {}(+5))
# returns first n elements of a list
take z [[[rec]]] ⧗ Number → (List a) → (List a)
rec 0 [[[case-take]]] case-end
case-take =?4 empty (2 : (5 --4 1))
case-end empty
:test (take (+2) ((+1) : ((+2) : {}(+3)))) ((+1) : {}(+2))
# takes elements while a predicate is satisfied
take-while z [[[rec]]] ⧗ (a → Boolean) → (List a) → (List a)
rec 0 [[[case-take]]] case-end
case-take 4 2 (2 : (5 4 1)) empty
case-end empty
:test (take-while zero? ((+0) : ((+0) : {}(+1)))) ((+0) : {}(+0))
# removes first n elements of a list
drop z [[[rec]]] ⧗ Number → (List a) → (List a)
rec 0 [[[case-drop]]] case-end
case-drop =?4 3 (5 --4 1)
case-end empty
:test (drop (+2) ((+1) : ((+2) : {}(+3)))) ({}(+3))
# removes elements from list while a predicate is satisfied
drop-while z [[[rec]]] ⧗ (a → Boolean) → (List a) → (List a)
rec 0 [[[case-drop]]] case-end
case-drop 4 2 (5 4 1) 3
case-end empty
:test (drop-while zero? ((+0) : ((+0) : {}(+1)))) ({}(+1))
# returns all tails of a list
tails z [[rec]] ⧗ (List a) → (List a)
rec ∅?0 {}empty (0 : (1 ~0))
:test (tails "abc") ("abc" : ("bc" : ("c" : {}empty)))
# returns all combinations of two lists
cross [[[[1 : 0] <$> 1] <++> 1]] ⧗ (List a) → (List b) → (List (Pair a b))
:test (cross "ab" "cd") (('a' : 'c') : (('a' : 'd') : (('b' : 'c') : {}('b' : 'd'))))
# returns all combinations of three lists
# TODO: add/use triple type (list element types can be different)
cross3 [[[[[[2 : (1 : {}0)] <$> 2] <++> 2] <++> 2]]] ⧗ (List a) → (List a) → (List a) → (List (List a))
# returns pair of take-while and drop-while
span [[(take-while 1 0) : (drop-while 1 0)]] ⧗ (a → Boolean) → (List a) → (Pair (List a) (List a))
:test (span (\lt? (+3)) ((+1) : ((+2) : ((+4) : {}(+1))))) (((+1) : {}(+2)) : ((+4) : {}(+1)))
# same as span but with inverted predicate
# slower but equivalent: span ∘ (…∘… ¬‣)
break [[left : right]] ⧗ (a → Boolean) → (List a) → (Pair (List a) (List a))
left take-while (¬‣ ∘ 1) 0
right drop-while (¬‣ ∘ 1) 0
:test (break (\gt? (+3)) ((+1) : ((+2) : ((+4) : {}(+1))))) (((+1) : {}(+2)) : ((+4) : {}(+1)))
# groups list by eq predicate
group-by z [[[rec]]] ⧗ (a → a → Boolean) → (List a) → (List (List a))
rec 0 [[[case-group]]] case-end
case-group build (span (4 2) 1)
build [(3 : ^0) : (6 5 ~0)]
case-end empty
:test (group-by [[(0 - 1) <? (+2)]] ((+1) : ((+2) : ((+3) : {}(+4))))) (((+1) : {}(+2)) : {}((+3) : {}(+4)))
# splits a list into a pair of two lists based on predicate, drops match
split-by [[left : right]] ⧗ (a → Boolean) → (List a) → (Pair (List a) (List a))
left take-while (¬‣ ∘ 1) 0
right fix (drop-while (¬‣ ∘ 1) 0)
fix [0 [[[1]]] empty]
:test (split-by (…=?… (+1)) ((+2) : ((+1) : ((+3) : {}(+2))))) ({}(+2) : ((+3) : {}(+2)))
# splits a list into lists based on predicate, drops match
split-list-by z [[[rec]]] ⧗ (a → Boolean) → (List a) → (List (List a))
rec 0 [[[case-split]]] case-end
case-split build (split-by 4 3)
build [^0 : (6 5 ~0)]
case-end empty
:test (split-list-by (…=?… (+1)) ((+2) : ((+1) : ((+3) : {}(+2))))) ({}(+2) : {}((+3) : {}(+2)))
# sorts a list of numbers in ascending order using non-inplace (obviously) quicksort
sort-asc z [[rec]]
rec 0 [[[case-sort]]] case-end
case-sort (4 lesser) ++ {}(2) ++ (4 greater)
lesser (\lt? 2) <#> 1
greater (\ge? 2) <#> 1
case-end empty
:test (sort-asc ((+3) : ((+2) : {}(+1)))) ((+1) : ((+2) : {}(+3)))
# sorts a list of numbers in descending order using non-inplace (obviously) quicksort
sort-desc z [[rec]]
rec 0 [[[case-sort]]] case-end
case-sort (4 greater) ++ {}(2) ++ (4 lesser)
greater (\ge? 2) <#> 1
lesser (\lt? 2) <#> 1
case-end empty
:test (sort-desc ((+1) : ((+2) : {}(+3)))) ((+3) : ((+2) : {}(+1)))
# inserts an element into a ascendingly sorted list
insert-sorted [go ∘ (span (\lt? 0))]
go [^0 ++ (1 : ~0)]
:test (insert-sorted (+3) ((+1) : ((+2) : {}(+4)))) ((+1) : ((+2) : ((+3) : {}(+4))))
# returns true if any element in a list matches a predicate
any? ⋁?‣ ∘∘ map ⧗ (a → Boolean) → (List a) → Boolean
:test (any? (\gt? (+2)) ((+1) : ((+2) : {}(+3)))) (true)
:test (any? (\gt? (+2)) ((+1) : ((+2) : {}(+2)))) (false)
# returns true if all elements in a list match a predicate
all? ⋀?‣ ∘∘ map ⧗ (a → Boolean) → (List a) → Boolean
:test (all? (\gt? (+2)) ((+3) : ((+4) : {}(+5)))) (true)
:test (all? (\gt? (+2)) ((+4) : ((+3) : {}(+2)))) (false)
# returns true if element is part of a list based on eq predicate
in? …∘… any? ⧗ (a → a → Boolean) → a → (List a) → Boolean
:test (in? …=?… (+3) ((+1) : ((+2) : {}(+3)))) (true)
:test (in? …=?… (+0) ((+1) : ((+2) : {}(+3)))) (false)
# returns true if all elements of one list are equal to corresponding elements of other list
eq? ⋀?‣ ∘∘∘ zip-with ⧗ (a → a → Boolean) → (List a) → Boolean
:test (eq? …=?… ((+1) : {}(+2)) ((+1) : {}(+2))) (true)
:test (eq? …=?… ((+1) : {}(+2)) ((+2) : {}(+2))) (false)
:test (eq? …=?… empty empty) (true)
# returns eq, lt, gt depending on comparison of two lists and comparison function
compare-case' z [[[[[[[rec]]]]]]] ⧗ Compare → a → b → c → (List a) → (List a) → d
rec ∅?1 (∅?0 4 2) (∅?0 3 (5 eq gt lt ^1 ^0))
eq 6 5 4 3 2 ~1 ~0
gt 3
lt 2
# returns 1 if a>b, -1 if a<b and 0 if a=b
# also: spaceship operator
compare' [compare-case' 0 (+0) (+1) (-1)] ⧗ Compare → Binary → Binary → Number
# returns true if list is prefix of other list
prefix? z [[[[rec]]]] ⧗ (a → a → Boolean) → (List a) → (List a) → Boolean
rec ∅?1 true (∅?0 false go)
go 1 [[2 [[(6 3 1) ⋀? (7 6 2 0)]]]]
:test (prefix? …=?… ((+1) : {}(+2)) ((+1) : {}(+2))) (true)
:test (prefix? …=?… ((+1) : {}(+2)) ((+0) : ((+1) : {}(+2)))) (false)
:test (prefix? …=?… ((+1) : {}(+2)) ((+2) : {}(+2))) (false)
:test (prefix? …=?… empty empty) (true)
# returns true if list is within other list
infix? [[[any? (prefix? 2 1) (tails 0)]]] ⧗ (a → a → Boolean) → (List a) → (List a) → Boolean
:test (infix? …=?… ((+1) : {}(+2)) ((+1) : {}(+2))) (true)
:test (infix? …=?… ((+1) : {}(+2)) ((+0) : ((+1) : {}(+2)))) (true)
:test (infix? …=?… ((+1) : {}(+2)) ((+2) : {}(+2))) (false)
:test (infix? …=?… empty empty) (true)
# finds the first index that matches a predicate
find-index z [[[rec]]] ⧗ (a → Boolean) → (List a) → Number
rec 0 [[[case-find]]] case-end
case-find (4 2) (+0) !(5 4 1)
!‣ [<?0 0 ++0]
case-end (-1)
:test (find-index (…=?… (+2)) ((+1) : ((+2) : ((+3) : {}(+2))))) ((+1))
:test (find-index (…=?… (+4)) ((+1) : ((+2) : ((+3) : {}(+2))))) ((-1))
# finds the first element that matches a predicate
find z [[[rec]]] ⧗ (a → Boolean) → (List a) → a
rec 0 [[[case-find]]] case-end
case-find (4 2) 2 (5 4 1)
case-end Ω
:test (find (…=?… (+2)) ((+1) : ((+2) : ((+3) : {}(+2))))) ((+2))
:test (find (…=?… (+1)) ((+1) : ((+2) : ((+3) : {}(+2))))) ((+1))
# removes first element that matches an eq predicate
remove z [[[[rec]]]] ⧗ (a → a → Boolean) → a → (List a) → (List a)
rec 0 [[[case-remove]]] case-end
case-remove (5 2 4) 1 (2 : (6 5 4 1))
case-end empty
:test (remove …=?… (+2) ((+1) : ((+2) : ((+3) : {}(+2))))) ((+1) : ((+3) : {}(+2)))
# removes duplicates from list based on eq predicate (keeps first occurrence)
nub z [[[rec]]] ⧗ (a → a → Boolean) → (List a) → (List a)
rec 0 [[[case-nub]]] case-end
case-nub 2 : (5 4 ([¬(5 0 3)] <#> 1))
case-end empty
:test (nub …=?… ((+1) : ((+2) : {}(+3)))) ((+1) : ((+2) : {}(+3)))
:test (nub …=?… ((+1) : ((+2) : {}(+1)))) ((+1) : {}(+2))
# returns a list with infinite-times an element
repeat z [[rec]] ⧗ a → (List a)
rec 0 : (1 0)
:test (take (+3) (repeat (+4))) ((+4) : ((+4) : {}(+4)))
# returns a list with n-times an element
replicate \(g take repeat) ⧗ Number → a → (List a)
:test (replicate (+3) (+4)) ((+4) : ((+4) : {}(+4)))
# pads a list at end until its length is n
pad-right [[[0 ++ (replicate |(2 - ∀0) 1)]]] ⧗ Number → a → (List a) → (List a)
:test (pad-right (+6) 'a' "hah") ("hahaaa")
# pads a list at start until its length is n
pad-left [[[(replicate (2 - ∀0) 1) ++ 0]]] ⧗ Number → a → (List a) → (List a)
:test (pad-left (+6) 'a' "hah") ("aaahah")
# returns an infinite list repeating a finite list
cycle z [[rec]] ⧗ (List a) → (List a)
rec 0 ++ (1 0)
:test (take (+6) (cycle "ab")) ("ababab")
# returns a list with infinite-times previous (or start) value applied to a function
iterate [z [[rec]]] ⧗ (a → a) → a → (List a)
rec 0 : (1 (2 0))
:test (take (+5) (iterate ++‣ (+0))) (((+0) : ((+1) : ((+2) : ((+3) : {}(+4))))))
:test (take (+2) (iterate (%‣ ∘ dec) (+5))) (((+5) : {}(+4)))
:test (take (+5) (iterate i (+4))) (take (+5) (repeat (+4)))
:test (take (+0) (iterate ++‣ (+0))) (empty)
# returns the nth element of infinite list iteration
# equivalent of index ∘∘ iterate
nth-iterate [z [[[rec]]]] ⧗ (a → a) → a → Number → a
rec =?0 1 (2 (3 1) --0)
:test (nth-iterate ++‣ (+0) (+5)) ((+5))
# enumerate list
enumerate zip (iterate ++‣ (+0)) ⧗ (List a) → (List (Pair Number a))
:test (enumerate "abc") (((+0) : 'a') : (((+1) : 'b') : {}((+2) : 'c')))
# calculates all fixed points of given functions as a list
y* [[[0 1] <$> 0] xs] ⧗ (List a) → (List b)
xs [[1 <! ([[1 2 0]] <$> 0)]] <$> 0
:test (&(+5) <$> (y* ([[[=?0 true (1 --0)]]] : {}[[[=?0 false (2 --0)]]]))) (false : {}true)
# similar to y* but passes arguments as a list
list-y* y [[&(1 0) <$> 0]] ⧗ (List a) → (List b)
:test (&(+5) <$> (list-y* ([[=?0 true (_1 --0)]] : {}[[=?0 false (^1 --0)]]))) (false : {}true)