Snake and coconut
I love Python. No, true, this is a great language, suitable for a wide range of tasks: here you have to work with the operating system, and web frameworks for every taste, and libraries for scientific calculations and data analysis. But besides Python, I like functional programming. And python in this regard is not bad: there are closures, anonymous functions, and in general, functions here are first-class objects. It would seem, what more could you ask for? And then I accidentally stumbled upon Coconut - a functional language compiled in Python. I ask all lovers of Python and FP under cat.
What? Functional language that compiles in Python? But why, because there are so many functional features, and if you want additionalperversions, i.e. module toolz.functoolz? But let's look at a simple task: we need to add the squares of numbers from some list.
l = [1, 2, 3, 4, 5]
Possible solutions
Imperative decision "in the forehead":
def sum_imp(lst):
s = 0
for n in lst:
s += n**2
return s
Using map and reduce (looks creepy):
from functools import reduce
from operator import add
def sum_map_reduce(lst):
return reduce(add, map(lambda n: n**2, lst))
Using list generators (pythonic-way):
def sum_list_comp(lst):
return sum([n**2 for n in lst])
The latter option is not so bad. But in such cases, I want to write something in the spirit
sum_sqr(lst) = lst |> map(n -> n**2) |> sum
Yes, just like in OCaml, but without strict typing (our language is dynamic). But what if I tell you that with Coconut we really can do that? Use it to write
sum_sqr(lst) = lst |> map$(n -> n**2) |> sum
and get a complete solution to the problem without function calls (from functions (from functions))).
Features
The authors of the language write that it adds the following features to Python:
- Pattern Matching
- Algebraic Data Types
- Destructive Assignment
- Partial application (I know about partial, but more below)
- Lazy lists (the same head :: tail from okamla)
- Function Composition
- Improved Lambda Expression Syntax
- Infix notation for functions
- Pipelines
- Optimization of tail recursion (Guido's opinion on this matter is known, but sometimes you want to)
- Parallel execution
It is also worth noting that the language can work in interpreter mode, compile into the Python source code and be used as a kernel for the Jupyter Notebook (I have not tested it myself yet, but the developers write what is possible).
And now let's dwell on some of the possibilities in more detail. All examples have been tested on Coconut 1.2.1.
Lambda Expression Syntax
I’m sure it’s not the only pain for me to write lambda expressions in python. I even think that it was specially created so that it was used as little as possible. Coconut makes the definition of the anonymous function exactly the way I would like to see it:
(x -> x*2)(a) # То же, что (lambda x: x*2)(a)
Function Composition
The composition of the functions here looks almost like a Haskell:
(f .. g .. h)(x) # То же, что и f(g(h(x)))
Partial application
The functools module has a partial function that allows you to create functions with fixed arguments. It has a significant drawback: positional arguments must be substituted strictly in order. For example, we need a function that raises numbers to the fifth power. Logically, we should use partial (we just want to take a function and fix one of the arguments!), But it won’t give any gain (pow in both cases is used to distract from the fact that this is a built-in operation):
from functools import partial
from operator import pow
def partial5(lst):
return map(lambda x: partial(pow(x, 5)), lst) # Какой кошмар!
def lambda5(lst):
return map(lambda x: pow(x, 5), lst) # Так немного лучше
What can Coconut offer? And here is what:
def coco5(lst) = map$(pow$(?, 5), lst)
The symbol $ immediately after the name of the function indicates its partial use, eh? used as a placeholder.
Pipelines
Another simple concept that is often used in functional languages and even in the well-known bash. In total there are 4 types of pipelines:
Pipeline | Title | Usage example | Explanation |
|> | simple direct | x |> f | f (x) |
<| | simple reverse | f <| x | f (x) |
| *> | multi-argument direct | x | *> f | f (* x) |
<* | | multi-argument reverse | f <* | x | f (* x) |
Pattern matching and algebraic types
In the simplest case, pattern matching looks like this:
match 'шаблон' in 'значение' if 'охранное выражение':
'код'
else:
'код'
Security and else block may be missing. In this form, pattern matching is not very interesting, so consider an example from the documentation:
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)])
As you might have guessed, Tree is a sum type that includes various types of binary tree nodes, and the depth function is used to recursively calculate the depth of a tree. The addpattern decorator allows dispatching using a template.
For cases where the result should be calculated depending on the first suitable template, the keyword case is entered . Here is an example of its use:
def classify_sequence(value):
'''Классификатор последовательностей'''
out = ""
case value:
match ():
out += "пусто"
match (_,):
out += "одиночка"
match (x,x):
out += "повтор "+str(x)
match (_,_):
out += "пара"
match _ is (tuple, list):
out += "последовательность"
else:
raise TypeError()
return out
Parallel execution
Coconut's parallel_map and concurrent_map are just wrappers over ProcessPoolExecutor and ThreadPoolExecutor from concurrent.futures. Despite their simplicity, they provide a simplified interface for multi-process / multi-threaded execution:
parallel_map(pow$(2), range(100)) |> list |> print
concurrent_map(get_data_for_user, all_users) |> list |> print
Conclusion
I was always envious that .Net has F #, under the JVM - Scala, Clojure, I generally keep silent about the number of functional languages compiled in JS. I finally found something similar for Python. I am pretty sure that Coconut will not get wide distribution, even though I would like to. After all, functional programming allows you to solve many problems concisely and elegantly. Often, even without loss of code readability.