Pythonic FP with Coconut
Anthony Khong
09 August 2018
Functional Programming
Declarative Programming by Example
Other Cool Features
Functional Programming
Immutability
Once assigned, a variable
cannot
change its value.
```python >>> def g(xs): ... ??? ... >>> def f(xs): ... return xs + [999] ... >>> xs = [1, 2, 3] >>> ys = g(xs) >>> f(xs) ??? ```
```python >>> def g(xs): ... xs.reverse() ... return xs ... >>> def f(xs): ... return xs + [999] ... >>> xs = [1, 2, 3] >>> ys = g(xs) >>> f(xs) [3, 2, 1, 999] ```
```python >>> def g(xs): ... return xs[::-1] ... >>> def f(xs): ... return xs + [999] ... >>> xs = [1, 2, 3] >>> ys = g(xs) >>> f(xs) [1, 2, 3, 999] ```
```haskell λ> g x = ??? λ> f x = x ++ [999] λ> xs = [1, 2, 3] λ> ys = g xs λ> f xs ??? ```
```haskell λ> g x = undefined λ> f x = x ++ [999] λ> xs = [1, 2, 3] λ> ys = g xs λ> f xs [1, 2, 3, 999] ```
```haskell λ> f x = x ++ [999] λ> xs = [1, 2, 3] λ> f xs [1, 2, 3, 999] ```
Immutability = Predictability
Declarative Programming
Declarative vs. Imperative
Say
what things are
rather than
how things are done
.
Sieve of Eratosthenes
Haskell
```haskell primes :: [Int] primes = sieve [2..] where sieve (x:xs) = x : sieve (filter (\n -> n `mod` x /= 0) xs) sieve [] = [] λ> takeWhile (<60) primes [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59] ```
Python
```python from itertools import count, takewhile def primes(): def sieve(numbers): head = next(numbers) yield head yield from sieve(n for n in numbers if n % head) return sieve(count(2)) >>> list(takewhile(lambda x: x < 60, primes())) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Coconut
```python from itertools import count, takewhile def primes(): def sieve(numbers): head = next(numbers) yield head yield from sieve(n for n in numbers if n % head) return sieve(count(2)) >>> list(takewhile(lambda x: x < 60, primes())) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Concise Lambdas
```python from itertools import count, takewhile def primes(): def sieve(numbers): head = next(numbers) yield head yield from sieve(n for n in numbers if n % head) return sieve(count(2)) >>> list(takewhile(x -> x < 60, primes())) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Forward Piping
```python from itertools import count, takewhile def primes(): def sieve(numbers): head = next(numbers) yield head yield from sieve(n for n in numbers if n % head) return sieve(count(2)) >>> primes() |> ns -> takewhile(x -> x < 60, ns) |> list [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Currying
```python from itertools import count, takewhile def primes(): def sieve(numbers): head = next(numbers) yield head yield from sieve(n for n in numbers if n % head) return sieve(count(2)) >>> primes() |> takewhile$(x -> x < 60) |> list [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Iterator Chaining
```python from itertools import count, takewhile def primes(): def sieve(numbers): head = next(numbers) return [head] :: sieve(n for n in numbers if n % head) return sieve(count(2)) >>> primes() |> takewhile$(x -> x < 60) |> list [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Pattern Matching
```python from itertools import count, takewhile def primes(): def sieve([head] :: tail): return [head] :: sieve(n for n in tail if n % head) return sieve(count(2)) >>> primes() |> takewhile$(x -> x < 60) |> list [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Function Assignments
```python from itertools import count, takewhile def primes() = def sieve([x] :: xs) = [x] :: sieve(n for n in xs if n % x) sieve(count(2)) >>> primes() |> takewhile$(x -> x < 60) |> list [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Builtin Higher-Order Functions
```python def primes() = def sieve([x] :: xs) = [x] :: sieve(n for n in xs if n % x) sieve(count(2)) >>> primes() |> takewhile$(x -> x < 60) |> list [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] ```
Coconut
```python def primes() = def sieve([x] :: xs) = [x] :: sieve(n for n in xs if n % x) sieve(count(2)) >>> primes() |> takewhile$(x -> x < 60) |> list ```
Python
```python from itertools import count, takewhile def primes(): def sieve(numbers): head = next(numbers) yield head yield from sieve(n for n in numbers if n % head) return sieve(count(2)) >>> list(takewhile(x -> x < 60, primes())) ```
Coconut
```python def primes() = def sieve([x] :: xs) = [x] :: sieve(n for n in xs if n % x) sieve(count(2)) >>> primes() |> takewhile$(x -> x < 60) |> list ```
Haskell
```haskell primes :: [Int] primes = sieve [2..] where sieve (x:xs) = x : sieve (filter (\n -> n `rem` x /= 0) xs) sieve [] = [] λ> takeWhile (<60) primes ```
Other Cool Features
Infix Functions
```python def a `mod` b = a % b print(321 `mod` 123) ```
Pattern Matching Functions
```python def last_two(_ + [a, b]): return a, b def xydict_to_xytuple({"x": x is int, "y": y is int}): return x, y ```
Destructuring Assigments
```python _ + [a, b] = [0, 1, 2, 3] {'a': x, 'b': y, **_} = {'a': 10, 'b': 123} [x] :: _ = primes() |> dropwhile$(x -> x < 5000) ```
Tail Call Optimisation
``` def is_even(0) = True @addpattern(is_even) def is_even(n is int if n > 0) = is_odd(n-1) def is_odd(0) = False @addpattern(is_odd) def is_odd(n is int if n > 0) = is_even(n-1) ```
Data Keyword
``` data Empty() data Leaf(n) data Node(l, r) Tree = (Empty, Leaf, Node) def depth(Tree()) = 0 @addpattern(depth) def depth(Tree(n)) = 1 @addpattern(depth) def depth(Tree(l, r)) = 1 + max([depth(l), depth(r)]) ```
MyPy + Nicer Typing
Coconut
```python def int_map(f: int -> int, xs: int[]) -> int[] = xs |> map$(f) |> list ```
Python
```python from typing import Callable, Sequence def int_map( f: Callable[[int], int], xs: Sequence[int] ) -> Sequence[Int]: return list(map(f, xs)) ```