x => expr
), or a function call ( f (x)
). That is, the whole code will look similar: id = x => x double = f => x => f (f (x))
True = t => f => t False = t => f => f
True
returns the first argument, False
returns the second argument. That is, the following statements are true: console.assert(1 == True (1) (2)) console.assert(2 == False (1) (2))
If
function: If = b => t => f => b (t) (f) console.assert(1 == If (True) (1) (2)) console.assert(2 == If (False) (1) (2))
if
, just syntactically looks a little different. With conditional branching, standard boolean operators can be easily implemented: Not = x => If (x) (False) (True) And = x => y => If (x) (y) (False) Or = x => y => If (x) (True) (y)
Pair = x => y => (f => f (x) (y)) Fst = p => p (True) Snd = p => p (False) p = Pair (1) (2) console.assert(1 == Fst (p)) console.assert(2 == Snd (p))
Triplet = x => y => z => (f => f (x) (y) (z)) Pentuplet = x => y => z => u => v => (f => f (x) (y) (z) (u) (v))
Byte
as a tuple of 8 logical values, Int
as a tuple of 4 Byte
and implement machine arithmetic on them, but this is a routine matter and will not bring any pleasure. There is a more beautiful way to describe natural numbers - Church arithmetic. Zero = s => z => z One = s => z => s (z) Two = s => z => s (s (z)) Three = s => z => s (s (s (z)))
n
times. For their construction, in essence, only zero and the function +1
are needed: Succ = n => (s => z => s (n (s) (z)))
Succ
throws another call s
on the left of the existing call chain, thereby returning to us the next in order natural number. If this function seems complicated, there is an alternative option. The result of his work will be exactly the same, but the s
sling here takes place on the right: Succ = n => (s => z => n (s) (s (z)))
int
is exactly the application of the function x => x + 1
to zero n
times: toInt = n => n (x => x + 1) (0) console.assert(0 == toInt (Zero)) console.assert(1 == toInt (One)) console.assert(2 == toInt (Two))
Add = n => m => m (Succ) (n) Mul = n => m => m (Add (n)) (Zero) Pow = n => p => p (Mul (n)) (One) //⇈ = n => m => m (Pow (n)) (One)
Sub = n => m => m (Pred) (n)
Pred = n => Fst (n (p => Pair (Snd (p)) (Succ (Snd (p)))) (Pair (Zero) (Zero)))
(n-1, n)
as follows: apply the function (x, y) -> (y, y+1)
to the pair (0, 0)
n
times and extract the left component from the result. As a consequence, it is easy to see that the number previous to zero will also be zero. It saves from uncertainty and gives many other advantages. IsZero = n => n (_ => False) (True) Lte = n => m => IsZero (Sub (n) (m)) Lt = n => m => Lte (Succ (n)) (m) Eq = n => m => And (Lte (n) (m)) (Lte (m) (n)) Max = n => m => If (Lte (n) (m)) (m) (n) Min = n => m => If (Lte (n) (m)) (n) (m)
Nil = f => x => x L1 = f => x => f (a0) (x) L2 = f => x => f (a0) (f (a1) (x)) L3 = f => x => f (a0) (f (a1) (f (a2) (x))) //...
False
, Zero
and Nil
are the same functions. Foldr = f => z => l => l (f) (z)
Append = a => l => (f => x => f (a) (l (f) (x))) Head = l => l (a => _ => a) () list = Append (1) (Append (2) (Nil)) console.assert(1 == Head (list))
Head
is the head of an empty list. In the correct program, this value should never be used, so I chose the syntactically smallest value. Generally speaking, within these brackets you can write anything. Later in the article there will be another place where I will use empty brackets for the exact same reason. Tail = l => Fst ( l (a => p => Pair (Snd (p)) (Append (a) (Snd (p)))) (Pair (Nil) (Nil)) ) console.assert(2 == Head (Tail (list)))
map
function and some more others: Map = m => l => (f => x => l (a => f (m (a))) (x)) Length = l => Foldr (_ => Succ) (Zero) (l) IsEmpty = l => IsZero (Length (l)) // JavaScript toList = l => Foldr (x => y => [x].concat(y)) ([]) (l) toIntList = l => toList (Map (toInt) (l)) function arraysEqual(a,b) { return !(a < b) && !(b < a); } //
Map
replaces each list item with the result of calling function f
on it.Length
and IsEmpty
speak for themselves. toList
and toIntList
will be useful for testing and for displaying lists in the console.OnNonEmpty : fun => list => result
function OnNonEmpty : fun => list => result
, which will call the fun
function on the list
only if it is not empty. Let's try: OnNonEmpty = f => l => If (IsEmpty (l)) (Nil) (f (l))
f
does not stop on an empty list, then OnNonEmpty
will not stop, although it should. The fact is that JavaScript provides us with an applicative order of calculations, that is, all the arguments of a function are evaluated before it is called, no laziness. And the If
statement should calculate only one branch, depending on the condition. Therefore, the function must be rewritten, and it will not become more beautiful from it. OnNonEmpty = f => l => (If (IsEmpty (l)) (_ => Nil) (_ => f (l))) ()
If
, depending on the condition, returns the function to be evaluated. And only after that the calculation is made, only in this way can we guarantee laziness.Z
, shamelessly peeped in this wonderful article . Z = f => (x => f (y => x (x) (y))) (x => f (y => x (x) (y)))
Z (f) == f (Z (f))
, that is, Z(f)
is such a value of x
that x == f (x)
. Hence the term "fixed point". But one should not think that by some miracle the combinator is capable of solving equations, instead it represents an infinite recursive call Z(f) = f (f (f (f ( ... ))))
. MyFun = Z (myFun => ...)
MyFun = (myFun => ...) MyFun //
MyFun
function MyFun
, and we can make real recursive calls in it. Let's try on the example of finding the remainder of the division: Rem = Z (rem => n => m => ( If (Lt (n) (m)) (_ => n) (_ => rem (Sub (n) (m)) (m)) ) ()) console.assert(1 == toInt (Rem (Three) (Two))) console.assert(0 == toInt (Rem (Three) (One)))
Gcd = Z (gcd => n => m => ( If (IsZero (m)) (_ => n) (_ => gcd (m) (Rem (n) (m))) ) ())
Seq = head => tail => Pair (head) (tail) SeqHead = seq => Fst (seq) SeqTail = seq => (Snd (seq)) ()
n
elements: SeqTake = Z (take => n => seq => ( If (IsZero (n)) (_ => Nil) (_ => Append (SeqHead (seq)) (take (Pred (n)) (SeqTail (seq)))) ) ())
SeqTake
without recursion. It is possible, I guarantee. Nat = (Z (natFrom => n => Seq (n) (_ => natFrom (Succ (n))))) (Zero) console.assert(arraysEqual([0, 1, 2], toIntList (SeqTake (Three) (Nat))))
natFrom (n)
, which returns a sequence of natural numbers starting with n
. Nat
is just the result of natFrom (Zero)
. SeqFilter = Z (filter => cond => seq => ( If (cond (SeqHead (seq))) (_ => Seq (SeqHead (seq)) (_ => filter (cond) (SeqTail (seq)))) (_ => filter (cond) (SeqTail (seq))) ) ())
SeqFilter
returns the filtered tail. Otherwise, it will be a sequence of the current head and the same filtered tail. Primes = (Z (sieve => nums => Seq (SeqHead (nums)) (_ => sieve (SeqFilter (p => Not (IsZero (Rem (p) (SeqHead (nums))))) (SeqTail (nums))) ))) (SeqTail (SeqTail (Nat)))
sieve (nums) { p = head (nums) rest = tail (nums) return append (p, sieve (filter (n -> n % p != 0, rest))) } primes = sieve [2, 3, 4, ...]
Ten = Mul (Two) (Add (Two) (Three)) // s => z => s (s (s (s (s (s (s (s (s (s (z)))))))))) console.assert(arraysEqual([2, 3, 5, 7, 11, 13, 17, 19, 23, 29], toIntList (SeqTake (Ten) (Primes))))
Source: https://habr.com/ru/post/322052/
All Articles