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