PK wJm$! coconut-v1.2.3/.buildinfo# Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. config: tags: PK wJE: : coconut-v1.2.3/objects.inv# Sphinx inventory version 2 # Project: Coconut # Version: 1.2.3 # The remainder of this file is compressed using zlib. xڅA0E=EטuHtM@텖FMf?ad{-4k*@e[ dCU)4z;q3Z`Mc#8HГ(BhJ ~sGNc>XKѪL֖r}j+h2yiQ~O ӛXW5߆\{j /HSPK wJ_ܪ ܪ coconut-v1.2.3/index.html
Yes and yes! Coconut compiles to Python, so Coconut modules are accessible from Python and Python modules are accessible from Coconut, including the entire Python standard library.
Coconut supports any Python version >= 2.6
on the 2.x
branch or >= 3.2
on the 3.x
branch. See compatible Python versions for more information.
Information on every Coconut release is chronicled on the GitHub releases page. There you can find all of the new features and breaking changes introduced in each release.
No problem—just use Coconut’s recursive_iterator
decorator and you should be fine. This is a known Python issue but recursive_iterator
will fix it for you.
You’re exactly the person Coconut was built for! Coconut lets you keep doing the thing you do well—write Python—without having to worry about annoyances like version compatibility, while also allowing you to do new cool things you might never have thought were possible before like pattern-matching and lazy evaluation. If you’ve ever used a functional programming language before, you’ll know that functional code is often much simpler, cleaner, and more readable (but not always, which is why Coconut isn’t purely functional). Python is a wonderful imperative language, but when it comes to modern functional programming—which, in Python’s defense, it wasn’t designed for—Python falls short, and Coconut corrects that shortfall.
Ease of debugging has long been a problem for all compiled languages, including languages like C
and C++
that these days we think of as very low-level languages. The solution to this problem has always been the same: line number maps. If you know what line in the compiled code corresponds to what line in the source code, you can easily debug just from the source code, without ever needing to deal with the compiled code at all. In Coconut, this can easily be accomplished by passing the --line-numbers
or -l
flag, which will add a comment to every line in the compiled code with the number of the corresponding line in the source code. Alternatively, --keep-lines
or -k
will put in the verbatim source line instead of or in addition to the source line number. Then, if Python raises an error, you’ll be able to see from the snippet of the compiled code that it shows you a comment telling you what line in your source code you need to look at to debug the error.
Definitely! While Coconut is great for functional programming, it also has a bunch of other awesome features as well, including the ability to compile Python 3 code into universal Python code that will run the same on any version. And that’s not even mentioning all of the features like pattern-matching and destructuring assignment with utility extending far beyond just functional programming. That being said, I’d highly recommend you give functional programming a shot, and since Coconut isn’t purely functional, it’s a great introduction to the functional style.
Yes, absolutely! Coconut’s tutorial assumes absolutely no prior knowledge of functional programming, only Python. Because Coconut is not a purely functional programming language, and all valid Python is valid Coconut, Coconut is a great introduction to functional programming. If you learn Coconut, you’ll be able to try out a new functional style of programming without having to abandon all the Python you already know and love.
Maybe. If you know the very basics of Python, and are also very familiar with functional programming, then definitely—Coconut will let you continue to use all your favorite tools of functional programming while you make your way through learning Python. If you’re not very familiar either with Python, or with functional programming, then you may be better making your way through a Python tutorial before you try learning Coconut. That being said, using Coconut to compile your pure Python code might still be very helpful for you, since it will alleviate having to worry about version incompatibility.
The short answer is that Python isn’t purely functional, and all valid Python is valid Coconut. The long answer is that Coconut isn’t purely functional for the same reason Python was never purely imperative—different problems demand different approaches. Coconut is built to be useful, and that means not imposing constraints about what style the programmer is allowed to use. That being said, Coconut is built specifically to work nicely when programming in a functional style, which means if you want to write all your code purely functionally, Coconut will make it a smooth experience, and allow you to have good-looking code to show for it.
I certainly hope not! Unlike most transpiled languages, all valid Python is valid Coconut. Coconut’s goal isn’t to replace Python, but to extend it. If a newbie learns Coconut, it won’t mean they have a harder time learning Python, it’ll mean they already know Python. And not just any Python, the newest and greatest—Python 3. And of course, Coconut is perfectly interoperable with Python, and uses all the same libraries—thus, Coconut can’t split the Python community, because the Coconut community is the Python community.
That’s great! Coconut is completely open-source, and new contributors are always welcome. Check out Coconut’s contributing guidelines for more information.
If you don’t get the reference, the image above is from Monty Python and the Holy Grail, in which the Knights of the Round Table bang Coconuts together to mimic the sound of riding a horse. The name was chosen to reference the fact that Python is named after Monty Python as well.
Evan Hubinger is an undergraduate student studying mathematics and computer science at Harvey Mudd College. He can be reached by asking a question on Coconut’s Gitter chat room, through email at evanjhub@gmail.com, or on LinkedIn.
Welcome to the tutorial for the Coconut Programming Language! Coconut is a variant of Python built for simple, elegant, Pythonic functional programming. But those are just words; what they mean in practice is that all valid Python 3 is valid Coconut but Coconut builds on top of Python a suite of simple, elegant utilities for functional programming.
Why use Coconut? Coconut is built to be useful. Coconut enhances the repertoire of Python programmers to include the tools of modern functional programming, in such a way that those tools are easy to use and immensely powerful; that is, Coconut does to functional programming what Python did to imperative programming. And Coconut code runs the same on any Python version, making the Python 2/3 split a thing of the past.
Specifically, Coconut adds to Python built-in, syntactical support for:
and much more!
At its very core, Coconut is a compiler that turns Coconut code into Python code. That means that anywhere where you can use a Python script, you can also use a compiled Coconut script. To access that core compiler, Coconut comes with a command-line utility, which can
Installing Coconut, including all the features above, is drop-dead simple. Just
pip install coconut
Note: Try re-running the above command with the --user
option if you are encountering errors. Be sure that the coconut
installation location (/usr/local/bin
or ${HOME}/.local/bin/
on UNIX machines) is in your PATH environment variable.
To check that your installation is functioning properly, try entering into the command line
coconut -h
which should display Coconut’s command-line help.
Note: If you’re having trouble installing Coconut, or if anything else mentioned in this tutorial doesn’t seem to work for you, feel free to ask for help on Gitter and somebody will try to answer your question as soon as possible.
Now that you’ve got Coconut installed, the obvious first thing to do is to play around with it. To launch the Coconut interpreter, just go to the command line and type
coconut
and you should see something like
Coconut Interpreter:
(type "exit()" or press Ctrl-D to end)
>>>
which is Coconut’s way of telling you you’re ready to start entering code for it to evaluate. So let’s do that!
In case you missed it earlier, all valid Python 3 is valid Coconut. That doesn’t mean compiled Coconut will only run on Python 3—in fact, compiled Coconut will run the same on any Python version—but it does mean that only Python 3 code is guaranteed to compile as Coconut code.
That means that if you’re familiar with Python, you’re already familiar with a good deal of Coconut’s core syntax and Coconut’s entire standard library. To show that, let’s try entering some basic Python into the Coconut interpreter.
>>> "hello, world!"
hello, world!
>>> 1 + 1
2
Of course, while being able to interpret Coconut code on-the-fly is a great thing, it wouldn’t be very useful without the ability to write and compile larger programs. To that end, it’s time to write our first Coconut program: “hello, world!” Coconut-style.
First, we’re going to need to create a file to put our code into. The recommended file extension for Coconut source files is .coco
, so let’s create the new file hello_world.coco
. After you do that, you should take the time now to set up your text editor to properly highlight Coconut code. For instructions on how to do that, see the documentation on Coconut syntax highlighting.
Now let’s put some code in our hello_world.coco
file. Unlike in Python, where headers like
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from __future__ import print_function, absolute_import, unicode_literals, division
are common and often very necessary, the Coconut compiler will automatically take care of all of that for you, so all you need to worry about is your own code. To that end, let’s add the code for our “hello, world!” program.
In pure Python 3, “hello, world!” is
print("hello, world!")
and while that will work in Coconut, equally as valid is to use a pipeline-style approach, which is what we’ll do, and write
"hello, world!" |> print
which should let you see very clearly how Coconut’s |>
operator enables pipeline-style programming: it allows an object to be passed along from function to function, with a different operation performed at each step. In this case, we are piping the object "hello, world!"
into the operation print
. Now let’s save our simple “hello, world!” program, and try to run it.
Compiling Coconut files and projects with the Coconut command-line utility is incredibly simple. Just type
coconut hello_world.coco
which should give the output
Coconut: Compiling hello_world.coco ...
Coconut: Compiled to hello_world.py .
and deposit a new hello_world.py
file in the same directory as the hello_world.coco
file. You should then be able to run that file with
python hello_world.py
which should produce hello, world!
as the output.
Compiling single files is not the only way to use the Coconut command-line utility, however. We can also compile all the Coconut files in a given directory simply by passing that directory as the first argument, which will get rid of the need to run the same Coconut header code in each file by storing it in a __coconut__.py
file in the same directory.
The Coconut compiler supports a large variety of different compilation options, the help for which can always be accessed by entering coconut -h
into the command line. One of the most useful of these is --linenumbers
(or -l
for short). Using --linenumbers
will add the line numbers of your source code as comments in the compiled code, allowing you to see what line in your source code corresponds to a line in the compiled code where an error is occurring, for ease of debugging.
Although all different types of programming can benefit from using more functional techniques, scientific computing, perhaps more than any other field, lends itself very well to functional programming, an observation the case studies in this tutorial are very good examples of. To that end, Coconut aims to provide extensive support for the established tools of scientific computing in Python.
That means supporting IPython/Jupyter, as modern Python programming, particularly in the sciences, has gravitated towards the use of IPython (the python kernel for the Jupyter framework) instead of the classic Python shell. Coconut supports being used as a kernel for Jupyter notebooks and consoles, allowing Coconut code to be used alongside powerful IPython features such as %magic
commands.
To launch a Jupyter notebook with Coconut as the kernel, use the command
coconut --jupyter notebook
and to launch a Jupyter console, use the command
coconut --jupyter console
or equivalently, --ipython
can be substituted for --jupyter
in either command.
Because Coconut is built to be fundamentally useful, the best way to demo it is to show it in action. To that end, the majority of this tutorial will be showing how to apply Coconut to solve particular problems, which we’ll call case studies.
These case studies are not intended to provide a complete picture of all of Coconut’s features. For that, see Coconut’s comprehensive documentation. Instead, they are intended to show how Coconut can actually be used to solve practical programming problems.
factorial
¶In the first case study we will be defining a factorial
function, that is, a function that computes n!
where n
is an integer >= 0
. This is somewhat of a toy example, since Python can fairly easily do this, but it will serve as a good showcase of some of the basic features of Coconut and how they can be used to great effect.
To start off with, we’re going to have to decide what sort of an implementation of factorial
we want. There are many different ways to tackle this problem, but for the sake of concision we’ll split them into four major categories: imperative, recursive, iterative, and addpattern
.
The imperative approach is the way you’d write factorial
in a language like C. Imperative approaches involve lots of state change, where variables are regularly modified and loops are liberally used. In Coconut, the imperative approach to the factorial
problem looks like this:
def factorial(n):
"""Compute n! where n is an integer >= 0."""
if n `isinstance` int and n >= 0:
acc = 1
for x in range(1, n+1):
acc *= x
return acc
else:
raise TypeError("the argument to factorial must be an integer >= 0")
# Test cases:
-1 |> factorial |> print # TypeError
0.5 |> factorial |> print # TypeError
0 |> factorial |> print # 1
3 |> factorial |> print # 6
Before we delve into what exactly is happening here, let’s give it a run and make sure the test cases check out. If we were really writing a Coconut program, we’d want to save and compile an actual file, but since we’re just playing around, let’s try copy-pasting into the interpreter. Here, you should get two TypeErrors
, then 1
, then 6
.
Now that we’ve verified it works, let’s take a look at what’s going on. Since the imperative approach is a fundamentally non-functional method, Coconut can’t help us improve this example very much. Even here, though, the use of Coconut’s infix notation (where the function is put in-between its arguments, surrounded in backticks) in n `isinstance` int
makes the code slightly cleaner and easier to read.
The recursive approach is the first of the fundamentally functional approaches, in that it doesn’t involve the state change and loops of the imperative approach. Recursive approaches avoid the need to change variables by making that variable change implicit in the recursive function call. Here’s the recursive approach to the factorial
problem in Coconut:
def factorial(n):
"""Compute n! where n is an integer >= 0."""
case n:
match 0:
return 1
match _ is int if n > 0:
return n * factorial(n-1)
else:
raise TypeError("the argument to factorial must be an integer >= 0")
# Test cases:
-1 |> factorial |> print # TypeError
0.5 |> factorial |> print # TypeError
0 |> factorial |> print # 1
3 |> factorial |> print # 6
Copy and paste the code and tests into the interpreter. You should get the same test results as you got for the imperative version—but you can probably tell there’s quite a lot more going on here than there. That’s intentional: Coconut is intended for functional programming, not imperative programming, and so its new features are built to be most useful when programming in a functional style.
Let’s take a look at the specifics of the syntax in this example. The first thing we see is case n
. This statement starts a case
block, in which only match
statements can occur. Each match
statement will attempt to match its given pattern against the value in the case
block. Only the first successful match inside of any given case
block will be executed. When a match is successful, any variable bindings in that match will also be performed. Additionally, as is true in this case, match
statements can also have if
guards that will check the given condition before the match is considered final. Finally, after the case
block, an else
statement is allowed, which will only be executed if no match
statement is.
Specifically, in this example, the first match
statement checks whether n
matches to 0
. If it does, it executes return 1
. Then the second match
statement checks whether n
matches to _ is int
, which performs an isinstance
check on n
against int
, then checks whether n > 0
, and if those are true, executes return n * factorial(n-1)
. If neither of those two statements are executed, the else
statement triggers and executes raise TypeError("the argument to factorial must be an integer >= 0")
.
Although this example is very basic, pattern-matching is both one of Coconut’s most powerful and most complicated features. As a general intuitive guide, it is helpful to think assignment whenever you see the keyword match
. A good way to showcase this is that all match
statements can be converted into equivalent destructuring assignment statements, which are also valid Coconut. In this case, the destructuring assignment equivalent to the factorial
function above would be:
def factorial(n):
"""Compute n! where n is an integer >= 0."""
try:
0 = n # destructuring assignment
except MatchError:
try:
_ is int = n # also destructuring assignment
except MatchError:
pass
else: if n > 0: # in Coconut, if, match, and try are allowed after else
return n * factorial(n-1)
else:
return 1
raise TypeError("the argument to factorial must be an integer >= 0")
# Test cases:
-1 |> factorial |> print # TypeError
0.5 |> factorial |> print # TypeError
0 |> factorial |> print # 1
3 |> factorial |> print # 6
First, copy and paste! While this destructuring assignment equivalent should work, it is much more cumbersome than match
statements when you expect that they’ll fail, which is why match
statement syntax exists. But the destructuring assignment equivalent illuminates what exactly the pattern-matching is doing, by making it clear that match
statements are really just fancy destructuring assignment statements, which are really just fancy normal assignment statements. In fact, to be explicit about using destructuring assignment instead of normal assignment, the match
keyword can be put before a destructuring assignment statement to signify it as such.
It will be helpful to, as we continue to use Coconut’s pattern-matching and destructuring assignment statements in further examples, think assignment whenever you see the keyword match
.
Up until now, for the recursive method, we have only dealt with pattern-matching, but there’s actually another way that Coconut allows us to improve our factorial
function. Coconut performs automatic tail call optimization, which means that whenever a function directly returns a call to another function, Coconut will optimize away the additional call. Thus, we can improve our factorial
function by rewriting it to use a tail call:
def factorial(n, acc=1):
"""Compute n! where n is an integer >= 0."""
case n:
match 0:
return acc
match _ is int if n > 0:
return factorial(n-1, acc*n)
else:
raise TypeError("the argument to factorial must be an integer >= 0")
# Test cases:
-1 |> factorial |> print # TypeError
0.5 |> factorial |> print # TypeError
0 |> factorial |> print # 1
3 |> factorial |> print # 6
Copy, paste! This new factorial
function is equivalent to the original version, with the exception that it will never raise a RuntimeError
due to reaching Python’s maximum recursion depth, since Coconut will optimize away the recursive tail call.
The final, and other functional, approach, is the iterative one. Iterative approaches avoid the need for state change and loops by using higher-order functions, those that take other functions as their arguments, like map
and reduce
, to abstract out the basic operations being performed. In Coconut, the iterative approach to the factorial
problem is:
def factorial(n):
"""Compute n! where n is an integer >= 0."""
case n:
match 0:
return 1
match _ is int if n > 0:
return range(1, n+1) |> reduce$(*)
else:
raise TypeError("the argument to factorial must be an integer >= 0")
# Test cases:
-1 |> factorial |> print # TypeError
0.5 |> factorial |> print # TypeError
0 |> factorial |> print # 1
3 |> factorial |> print # 6
Copy, paste! This definition differs from the recursive definition only by one line. That’s intentional: because both the iterative and recursive approaches are functional approaches, Coconut can provide a great assist in making the code cleaner and more readable. The one line that differs is this one:
return range(1, n+1) |> reduce$(*)
Let’s break down what’s happening on this line. First, the range
function constructs an iterator of all the numbers that need to be multiplied together. Then, it is piped into the function reduce$(*)
, which does that multiplication. But how? What is reduce$(*)
.
We’ll start with the base, the reduce
function. reduce
used to exist as a built-in in Python 2, and Coconut brings it back. reduce
is a higher-order function that takes a function on two arguments as its first argument, and an iterator as its second argument, and applies that function to the given iterator by starting with the first element, and calling the function on the accumulated call so far and the next element, until the iterator is exhausted. Here’s a visual representation:
reduce(f, (a, b, c, d))
acc iter
(a, b, c, d)
a (b, c, d)
f(a, b) (c, d)
f(f(a, b), c) (d)
f(f(f(a, b), c), d)
return acc
Now let’s take a look at what we do to reduce
to make it multiply all the numbers we feed into it together. The Coconut code that we saw for that was reduce$(*)
. There are two different Coconut constructs being used here: the operator function for multiplication in the form of (*)
, and partial application in the form of $
.
First, the operator function. In Coconut, a function form of any operator can be retrieved by surrounding that operator in parentheses. In this case, (*)
is roughly equivalent to lambda x, y: x*y
, but much cleaner and neater. In Coconut’s lambda syntax, (*)
is also equivalent to (x, y) -> x*y
, which we will use from now on for all lambdas, even though both are legal Coconut, because Python’s lambda
statement is too ugly and bulky to use regularly. In fact, if Coconut’s --strict
mode is enabled, which will force your code to obey certain cleanliness standards, it will raise an error whenever Python lambda
statements are used.
Second, the partial application. Think of partial application as lazy function calling, and $
as the lazy-ify operator, where lazy just means “don’t evaluate this until you need to”. In Coconut, if a function call is prefixed by a $
, like in this example, instead of actually performing the function call, a new function is returned with the given arguments already provided to it, so that when it is then called, it will be called with both the partially-applied arguments and the new arguments, in that order. In this case, reduce$(*)
is equivalent to (*args, **kwargs) -> reduce((*), *args, **kwargs)
.
Putting it all together, we can see how the single line of code
range(1, n+1) |> reduce$(*)
is able to compute the proper factorial, without using any state or loops, only higher-order functions, in true functional style. By supplying the tools we use here like partial application ($
), pipeline-style programming (|>
), higher-order functions (reduce
), and operator functions ((*)
), Coconut enables this sort of functional programming to be done cleanly, neatly, and easily.
addpattern
Method¶While the iterative approach is very clean, there are still some bulky pieces—looking at the iterative version below, you can see that it takes three entire indentation levels to get from the function definition to the actual objects being returned:
def factorial(n):
"""Compute n! where n is an integer >= 0."""
case n:
match 0:
return 1
match _ is int if n > 0:
return range(1, n+1) |> reduce$(*)
else:
raise TypeError("the argument to factorial must be an integer >= 0")
By making use of the built-in Coconut function addpattern
, we can take that from three indentation levels down to one. Take a look:
def factorial(0) = 1
@addpattern(factorial)
def factorial(n is int if n > 0) =
"""Compute n! where n is an integer >= 0."""
range(1, n+1) |> reduce$(*)
# Test cases:
-1 |> factorial |> print # MatchError
0.5 |> factorial |> print # MatchError
0 |> factorial |> print # 1
3 |> factorial |> print # 6
Copy, paste! This should work exactly like before, except now it raises MatchError
as a fall through instead of TypeError
. There are three major new concepts to talk about here: addpattern
, of course, assignment funciton notation, and pattern-matching function definition—how both of the functions above are defined.
First, assignment function notation. This one’s pretty straightforward. If a function is defined with an =
instead of a :
, the last line is required to be an expression, and is automatically returned.
Second, pattern-matching function definition. Pattern-matching function definition does exactly that—pattern-matches against all the arguments that are passed to the function. There are a couple of things to watch out for when using pattern-matching function definition, however. First, that if the pattern doesn’t match (if for example the wrong number of arguments are passed), your function will raise a MatchError
, and second, that keyword arguments aren’t allowed. Finally, like destructuring assignment, if you want to be more explicit about using pattern-matching function definition, you can add a match
before the def
.
Third, addpattern
. addpattern
takes one argument, a previously-defined pattern-matching function, and returns a decorator that decorates a new pattern-matching function by adding the new pattern as an additional case to the old patterns. Thus, addpattern
can be thought of as doing exactly what it says—it adds a new pattern to an existing pattern-matching function.
Finally, not only can we rewrite the imperative approach using addpattern
, as we did above, we can also rewrite the recursive approach using addpattern
, like so:
def factorial(0) = 1
@addpattern(factorial)
def factorial(n is int if n > 0) =
"""Compute n! where n is an integer >= 0."""
n * factorial(n - 1)
# Test cases:
-1 |> factorial |> print # MatchError
0.5 |> factorial |> print # MatchError
0 |> factorial |> print # 1
3 |> factorial |> print # 6
Copy, paste! It should work exactly like before, except, as above, with TypeError
replaced by MatchError
.
quick_sort
¶In the second case study, we will be implementing the quick sort algorithm. We will implement two versions: first, a quick_sort
function that takes in a list and outputs a list, and second, a quick_sort
function that takes in an iterator and outputs an iterator.
First up is quick_sort
for lists. We’re going to use a recursive addpattern
-based approach to tackle this problem—a similar approach to the very last factorial
function we wrote. That’s because since we’re not going to write quick_sort
in a tail-recursive style, we can’t use tail_recursive
, and thus there’s no reason to write the whole thing as one function and we might as well use addpattern
to reduce the amount of indentation we’re going to need. Without further ado, here’s our implementation of quick_sort
for lists:
def quick_sort([]) = []
@addpattern(quick_sort)
def quick_sort([head] + tail) =
"""Sort the input sequence using the quick sort algorithm."""
(quick_sort([x for x in tail if x < head])
+ [head]
+ quick_sort([x for x in tail if x >= head]))
# Test cases:
[] |> quick_sort |> print # []
[3] |> quick_sort |> print # [3]
[0,1,2,3,4] |> quick_sort |> print # [0,1,2,3,4]
[4,3,2,1,0] |> quick_sort |> print # [0,1,2,3,4]
[3,0,4,2,1] |> quick_sort |> print # [0,1,2,3,4]
Copy, paste! Only one new feature here: head-tail pattern-matching. Here, we see the head-tail pattern [head] + tail
, which more generally just follow the form of a list or tuple added to a variable. When this appears in any pattern-matching context, the value being matched against will be treated as a sequence, the list or tuple matched against the beginning of that sequence, and the rest of it bound to the variable. In this case, we use the head-tail pattern to remove the head so we can use it as the pivot for splitting the rest of the list.
Now it’s time to try quick_sort
for iterators. Our method for tackling this problem is going to be a combination of the recursive and iterative approaches we used for the factorial
problem, in that we’re going to be lazily building up an iterator, and we’re going to be doing it recursively. Here’s the code:
def quick_sort(l):
"""Sort the input iterator, using the quick sort algorithm, and without using any data until necessary."""
match [head] :: tail in l:
tail, tail_ = tee(tail)
yield from (quick_sort((x for x in tail if x < head))
:: (head,)
:: quick_sort((x for x in tail_ if x >= head))
)
# Test cases:
[] |> quick_sort |> list |> print # []
[3] |> quick_sort |> list |> print # [3]
[0,1,2,3,4] |> quick_sort |> list |> print # [0,1,2,3,4]
[4,3,2,1,0] |> quick_sort |> list |> print # [0,1,2,3,4]
[3,0,4,2,1] |> quick_sort |> list |> print # [0,1,2,3,4]
Copy, paste! This quick_sort
algorithm works uses a bunch of new constructs, so let’s go over them.
First, the ::
operator, which appears here both in pattern-matching and by itself. In essence, the ::
operator is lazy +
for iterators. On its own, it takes two iterators and concatenates, or chains, them together, and it does this lazily, not evaluating anything until its needed, so it can be used for making infinite iterators. In pattern-matching, it inverts that operation, destructuring the beginning of an iterator into a pattern, and binding the rest of that iterator to a variable.
Which brings us to the second new thing, match ... in ...
notation. The notation
match pattern in item:
<body>
else:
<else>
is shorthand for
case item:
match pattern:
<body>
else:
<else>
that avoids the need for an additional level of indentation when only one match
is being performed.
The third new construct is the built-in function tee
. tee
solves a problem for functional programming created by the implementation of Python’s iterators: whenever an element of an iterator is accessed, it’s lost. tee
solves this problem by splitting an iterator in two (or more if the optional argument n
is passed) independent iterators that both use the same underlying iterator to access their data, thus when an element of one is accessed, it isn’t lost in the other.
Finally, although it’s not a new construct, since it exists in Python 3, the use of yield from
here deserves a mention. In Python, yield
is the statement used to construct iterators, functioning much like return
, with the exception that multiple yield
s can be encountered, and each one will produce another element. yield from
is very similar, except instead of adding a single element to the produced iterator, it adds another whole iterator.
Putting it all together, here’s our quick_sort
function again:
def quick_sort(l):
"""Sort the input iterator, using the quick sort algorithm, and without using any data until necessary."""
match [head] :: tail in l:
tail, tail_ = tee(tail)
yield from (quick_sort((x for x in tail if x < head))
:: (head,)
:: quick_sort((x for x in tail_ if x >= head))
)
The function first attempts to split l
into an initial element and a remaining iterator. If l
is the empty iterator, that match will fail, and it will fall through, yielding the empty iterator. Otherwise, we make a copy of the rest of the iterator, and yield the join of (the quick sort of all the remaining elements less than the initial element), (the initial element), and (the quick sort of all the remaining elements greater than the initial element).
The advantages of the basic approach used here, heavy use of iterators and recursion, as opposed to the classical imperative approach, are numerous. First, our approach is more clear and more readable, since it is describing what quick_sort
is instead of how quick_sort
could be implemented. Second, our approach is lazy in that our quick_sort
won’t evaluate any data until it needs it. Finally, and although this isn’t relevant for quick_sort
it is relevant in many other cases, an example of which we’ll see later in this tutorial, our approach allows for working with infinite series just like they were finite.
And Coconut makes programming in such an advantageous functional approach significantly easier. In this example, Coconut’s pattern-matching lets us easily split the given iterator, and Coconut’s ::
iterator joining operator lets us easily put it back together again in sorted order.
vector
Part I¶In the next case study, we’ll be doing something slightly different—instead of defining a function, we’ll be creating an object. Specifically, we’re going to try to implement an immutable n-vector that supports all the basic vector operations.
In functional programming, it is often very desirable to define immutable objects, those that can’t be changed once created—like Python’s strings or tuples. Like strings and tuples, immutable objects are useful for a wide variety of reasons:
Coconut’s data
statement brings the power and utility of immutable, algebraic data types to Python, and it is this that we will be using to construct our vector
type. The demonstrate the syntax of data
statements, we’ll start by defining a simple 2-vector. Our vector will have one special method __abs__
which will compute the vector’s magnitude, defined as the square root of the sum of the squares of the elements. Here’s our 2-vector:
data vector2(x, y):
"""Immutable 2-vector."""
def __abs__(self) =
"""Return the magnitude of the 2-vector."""
(self.x**2 + self.y**2)**0.5
# Test cases:
vector2(1, 2) |> print # vector2(x=1, y=2)
vector2(3, 4) |> abs |> print # 5
vector2(1, 2) |> fmap$(x -> x*2) |> print # vector2(x=2, y=4)
v = vector2(2, 3)
v.x = 7 # AttributeError
Copy, paste! This example shows the basic syntax of data
statements:
data <name>(<attributes>):
<body>
where <name>
and <body>
are the same as the equivalent class
definition, but <attributes>
are the different attributes of the data type, in order that the constructor should take them as arguments. In this case, vector2
is a data type of two attributes, x
and y
, with one defined method, __abs__
, that computes the magnitude. As the test cases show, we can then create, print, but not modify instances of vector2
.
One other thing to call attention to here is the use of fmap
. fmap
is a Coconut built-in that allows you to map functions over algebraic data types. In fact, Coconut’s data
types support iteration, so the standard map
works on them, but it doesn’t return another object of the same data type. Thus, fmap
is simply map
plus a call to the object’s constructor.
Now that we’ve got the 2-vector under our belt, let’s move to back to our original, more complicated problem: n-vectors, that is, vectors of arbitrary length. We’re going to try to make our n-vector support all the basic vector operations, but we’ll start out with just the data
definition and the constructor:
data vector(*pts):
"""Immutable n-vector."""
def __new__(cls, *pts):
"""Create a new vector from the given pts."""
match (v is vector,) in pts:
return v # vector(v) where v is a vector should return v
else:
return pts |*> datamaker(cls) # accesses base constructor
# Test cases:
vector(1, 2, 3) |> print # vector(*pts=(1, 2, 3))
vector(4, 5) |> vector |> print # vector(*pts=(4, 5))
Copy, paste! The big new thing here is how to write data
constructors. Since data
types are immutable, __init__
construction won’t work. Instead, a different special method __new__
is used, which must return the newly constructed instance, and unlike most methods, takes the class not the object as the first argument. Since __new__
needs to return a fully constructed instance, in almost all cases it will be necessary to access the underlying data
constructor. To achieve this, Coconut provides the built-in function datamaker
, which takes a data type, often the first argument to __new__
, and returns its underlying data
constructor.
In this case, the constructor checks whether nothing but another vector
was passed, in which case it returns that, otherwise it returns the result of creating a tuple of the arguments and passing that to the underlying constructor, the form of which is vector(*pts)
, since that is how we declared the data type.
The other new construct used here is the |*>
, or star-pipe, operator, which functions exactly like the normal pipe, except that instead of calling the function with one argument, it calls it with as many arguments as there are elements in the sequence passed into it. The difference between |*>
and |>
is exactly analogous to the difference between f(args)
and f(*args)
.
Now that we have a constructor for our n-vector, it’s time to write its methods. First up is __abs__
, which should compute the vector’s magnitude. This will be slightly more complicated than with the 2-vector, since we have to make it work over an arbitrary number of pts
. Fortunately, we can use Coconut’s pipeline-style programming and partial application to make it simple:
def __abs__(self) =
"""Return the magnitude of the vector."""
self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5)
The basic algorithm here is map square over each element, sum them all, then square root the result. The one new construct used here is the use of a ?
in partial application, which simply allows skipping an argument from being partially applied and deferring it to when the function is called. In this case, the ?
lets us partially apply the exponent instead of the base in pow
(we could also have equivalently used (**)
).
Next up is vector addition. The goal here is to add two vectors of equal length by adding their components. To do this, we’re going to make use of Coconut’s ability to perform pattern-matching, or in this case destructuring assignment, to data types, like so:
def __add__(self, other) =
"""Add two vectors together."""
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((+), self.pts, other_pts) |*> vector
There are a couple of new constructs here, but the main notable one is the destructuring assignment statement vector(*other_pts) = other
which showcases the syntax for pattern-matching against data types: it mimics exactly the original data
declaration of that data type. In this case, vector(*other_pts) = other
will only match a vector, raising a MatchError
otherwise, and if it does match a vector, will assign the vector’s pts
attribute to the variable other_pts
.
Next is vector subtraction, which is just like vector addition, but with (-)
instead of (+)
:
def __sub__(self, other) =
"""Subtract one vector from another."""
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((-), self.pts, other_pts) |*> vector
One thing to note here is that unlike the other operator functions, (-)
can either mean negation or subtraction, the meaning of which will be inferred based on how many arguments are passed, 1 for negation, 2 for subtraction. To show this, we’ll use the same (-)
function to implement vector negation, which should simply negate each element:
def __neg__(self) =
"""Retrieve the negative of the vector."""
self.pts |> map$((-)) |*> vector
Our next method will be equality. We’re again going to use data
pattern-matching to implement this, but this time inside of a match
statement instead of with destructuring assignment, since we want to return False
not raise an error if the match fails. Here’s the code:
def __eq__(self, other):
"""Compare whether two vectors are equal."""
match vector(*=self.pts) in other:
return True
else:
return False
The only new construct here is the use of =self.pts
in the match
statement. This construct is used to perform a check inside of the pattern-matching, making sure the match
only succeeds if other.pts == self.pts
.
The last method we’ll implement is multiplication. This one is a little bit tricky, since mathematically, there are a whole bunch of different ways to multiply vectors. For our purposes, we’re just going to look at two: between two vectors of equal length, we want to compute the dot product, defined as the sum of the corresponding elements multiplied together, and between a vector and a scalar, we want to compute the scalar multiple, which is just each element multiplied by that scalar. Here’s our implementation:
def __mul__(self, other):
"""Scalar multiplication and dot product."""
match vector(*other_pts) in other:
assert len(other_pts) == len(self.pts)
return map((*), self.pts, other_pts) |> sum # dot product
else:
return self.pts |> map$((*)$(other)) |*> vector # scalar multiple
def __rmul__(self, other) =
"""Necessary to make scalar multiplication commutative."""
self * other
The first thing to note here is that unlike with addition and subtraction, where we wanted to raise an error if the vector match failed, here, we want to do scalar multiplication if the match fails, so instead of using destructuring assignment, we use a match
statement. The second thing to note here is the combination of pipeline-style programming, partial application, operator functions, and higher-order functions we’re using to compute the dot product and scalar multiple. For the dot product, we map multiplication over the two vectors, then sum the result. For the scalar multiple, we take the original points, map multiplication by the scalar over them, then use them to make a new vector.
Finally, putting everything together:
data vector(*pts):
"""Immutable n-vector."""
def __new__(cls, *pts):
"""Create a new vector from the given pts."""
match (v is vector,) in pts:
return v # vector(v) where v is a vector should return v
else:
return pts |*> datamaker(cls) # accesses base constructor
def __abs__(self) =
"""Return the magnitude of the vector."""
self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5)
def __add__(self, other) =
"""Add two vectors together."""
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((+), self.pts, other_pts) |*> vector
def __sub__(self, other) =
"""Subtract one vector from another."""
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((-), self.pts, other_pts) |*> vector
def __neg__(self) =
"""Retrieve the negative of the vector."""
self.pts |> map$((-)) |*> vector
def __eq__(self, other):
"""Compare whether two vectors are equal."""
match vector(*=self.pts) in other:
return True
else:
return False
def __mul__(self, other):
"""Scalar multiplication and dot product."""
match vector(*other_pts) in other:
assert len(other_pts) == len(self.pts)
return map((*), self.pts, other_pts) |> sum # dot product
else:
return self.pts |> map$((*)$(other)) |*> vector # scalar multiplication
def __rmul__(self, other) =
"""Necessary to make scalar multiplication commutative."""
self * other
# Test cases:
vector(1, 2, 3) |> print # vector(*pts=(1, 2, 3))
vector(4, 5) |> vector |> print # vector(*pts=(4, 5))
vector(3, 4) |> abs |> print # 5
vector(1, 2) + vector(2, 3) |> print # vector(*pts=(3, 5))
vector(2, 2) - vector(0, 1) |> print # vector(*pts=(2, 1))
-vector(1, 3) |> print # vector(*pts=(-1, -3))
(vector(1, 2) == "string") |> print # False
(vector(1, 2) == vector(3, 4)) |> print # False
(vector(2, 4) == vector(2, 4)) |> print # True
2*vector(1, 2) |> print # vector(*pts=(2, 4))
vector(1, 2) * vector(1, 3) |> print # 7
Copy, paste! Now that was a lot of code. But looking it over, it looks clean, readable, and concise, and it does precisely what we intended it to do: create an algebraic data type for an immutable n-vector that supports the basic vector operations. And we did the whole thing without needing any imperative constructs like state or loops—pure functional programming.
vector_field
¶For the final case study, instead of me writing the code, and you looking at it, you’ll be writing the code—of course, I won’t be looking at it, but I will show you how I would have done it after you give it a shot by yourself.
The bonus challenge for this section is to write each of the functions we’ll be defining in just one line. Try using assignment functions to help with that!
With that out of the way, it’s time to introduce the general goal of this case study. We want to write a program that will allow us to produce infinite vector fields that we can iterate over and apply operations to. And in our case, we’ll say we only care about vectors with positive components.
Our first step, therefore, is going to be creating a field of all the points with positive x
and y
values—that is, the first quadrant of the x-y
plane, which looks something like this:
...
(0,2) ...
(0,1) (1,1) ...
(0,0) (1,0) (2,0) ...
But since we want to be able to iterate over that plane, we’re going to need to linearize it somehow, and the easiest way to do that is to split it up into diagonals, and traverse the first diagonal, then the second diagonal, and so on, like this:
...
(0,2)< ...
\_
(0,1)< (1,1)< ...
\_ \_
(0,0) > (1,0) > (2,0) > ...
diagonal_line
¶Thus, our first function diagonal_line(n)
should construct an iterator of all the points, represented as coordinate tuples, in the n
th diagonal, starting with (0, 0)
as the 0
th diagonal. Like we said at the start of this case study, this is where we I let go and you take over. Using all the tools of functional programming that Coconut provides, give diagonal_line
a shot. When you’re ready to move on, scroll down.
Here are some tests that you can use:
diagonal_line(0) `isinstance` (list, tuple) |> print # False (should be an iterator)
diagonal_line(0) |> list |> print # [(0, 0)]
diagonal_line(1) |> list |> print # [(0, 1), (1, 0)]
Hint: the n
th diagonal should contain n+1
elements, so try starting with range(n+1)
and then transforming it in some way.
That wasn’t so bad, now was it? Now, let’s take a look at my solution:
def diagonal_line(n) = range(n+1) |> map$((i) -> (i, n-i))
Pretty simple, huh? We take range(n+1)
, and use map
to transform it into the right sequence of tuples.
linearized_plane
¶Now that we’ve created our diagonal lines, we need to join them together to make the full linearized plane, and to do that we’re going to write the function linearized_plane()
. linearized_plane
should produce an iterator that goes through all the points in the plane, in order of all the points in the first diagonal(0)
, then the second diagonal(1)
, and so on. linearized_plane
is going to be, by necessity, an infinite iterator, since it needs to loop through all the points in the plane, which have no end. To help you accomplish this, remember that the ::
operator is lazy, and won’t evaluate its operands until they’re needed, which means it can be used to construct infinite iterators. When you’re ready to move on, scroll down.
Tests:
# Note: these tests use $[] notation, which we haven't introduced yet
# but will introduce later in this case study; for now, just run the
# tests, and make sure you get the same result as is in the comment
linearized_plane()$[0] |> print # (0, 0)
linearized_plane()$[:3] |> list |> print # [(0, 0), (0, 1), (1, 0)]
Hint: instead of defining the function as linearized_plane()
, try defining it as linearized_plane(n=0)
, where n
is the diagonal to start at, and use recursion to build up from there.
That was a little bit rougher than the first one, but hopefully still not too bad. Let’s compare to my solution:
def linearized_plane(n=0) = diagonal_line(n) :: linearized_plane(n+1)
As you can see, it’s a very fundamentally simple solution: just use ::
and recursion to join all the diagonals together in order.
vector_field
¶Now that we have a function that builds up all the points we need, it’s time to turn them into vectors, and to do that we’ll define the new function vector_field()
, which should turn all the tuples in linearized_plane
into vectors, using the n-vector class we defined earlier.
Tests:
# You'll need to bring in the vector class from earlier to make these work
vector_field()$[0] |> print # vector(*pts=(0, 0))
vector_field()$[2:3] |> list |> print # [vector(*pts=(1, 0))]
Hint: Remember, the way we defined vector it takes the components as separate arguments, not a single tuple.
We’re making good progress! Before we move on, check your solution against mine:
def vector_field() = linearized_plane() |> map$((xy) -> vector(*xy))
All we’re doing is taking our linearized_plane
and mapping vector
over it, but making sure to call vector with each element of the tuple as a separate argument.
Now that we’ve built all the functions we need for our vector field, it’s time to put it all together and test it. Feel free to substitute in your versions of the functions below:
data vector(*pts):
"""Immutable n-vector."""
def __new__(cls, *pts):
"""Create a new vector from the given pts."""
match (v is vector,) in pts:
return v # vector(v) where v is a vector should return v
else:
return pts |*> datamaker(cls) # accesses base constructor
def __abs__(self) =
"""Return the magnitude of the vector."""
self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5)
def __add__(self, other) =
"""Add two vectors together."""
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((+), self.pts, other_pts) |*> vector
def __sub__(self, other) =
"""Subtract one vector from another."""
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((-), self.pts, other_pts) |*> vector
def __neg__(self) =
"""Retrieve the negative of the vector."""
self.pts |> map$((-)) |*> vector
def __eq__(self, other):
"""Compare whether two vectors are equal."""
match vector(*=self.pts) in other:
return True
else:
return False
def __mul__(self, other):
"""Scalar multiplication and dot product."""
match vector(*other_pts) in other:
assert len(other_pts) == len(self.pts)
return map((*), self.pts, other_pts) |> sum # dot product
else:
return self.pts |> map$((*)$(other)) |*> vector # scalar multiplication
def __rmul__(self, other) =
"""Necessary to make scalar multiplication commutative."""
self * other
def diagonal_line(n) = range(n+1) |> map$((i) -> (i, n-i))
def linearized_plane(n=0) = diagonal_line(n) :: linearized_plane(n+1)
def vector_field() = linearized_plane() |> map$((xy) -> vector(*xy))
# Test cases:
diagonal_line(0) `isinstance` (list, tuple) |> print # False (should be an iterator)
diagonal_line(0) |> list |> print # [(0, 0)]
diagonal_line(1) |> list |> print # [(0, 1), (1, 0)]
linearized_plane()$[0] |> print # (0, 0)
linearized_plane()$[:3] |> list |> print # [(0, 0), (0, 1), (1, 0)]
vector_field()$[0] |> print # vector(*pts=(0, 0))
vector_field()$[2:3] |> list |> print # [vector(*pts=(1, 0))]
Copy, paste! Once you’ve made sure everything is working correctly if you substituted in your own functions, take a look at the last 4 tests. You’ll notice that they use a new notation, similar to the notation for partial application we saw earlier, but with brackets instead of parentheses. This is the notation for iterator slicing. Similar to how partial application was lazy function calling, iterator slicing is lazy sequence slicing. Like with partial application, it is helpful to think of $
as the lazy-ify operator, in this case turning normal Python slicing, which is evaluated immediately, into lazy iterator slicing, which is evaluated only when the elements in the slice are needed.
With that in mind, now that we’ve built our vector field, it’s time to use iterator slicing to play around with it. Try doing something cool to our vector fields like
magnitude_field
where each point is that vector’s magnitudemap
and the vector addition and multiplication methods we wrote earlierthen use iterator slicing to take out portions and examine them.
vector
Part II¶For the some of the applications you might want to use your vector_field
for, it might be desirable to add some useful methods to our vector
. In this case study, we’re going to be focusing on one in particular: .angle
.
.angle
will take one argument, another vector, and compute the angle between the two vectors. Mathematically, the formula for the angle between two vectors is the dot product of the vectors’ respective unit vectors. Thus, before we can implement .angle
, we’re going to need .unit
. Mathematically, the formula for the unit vector of a given vector is that vector divided by its magnitude. Thus, before we can implement .unit
, and by extension .angle
, we’ll need to start by implementing division.
__truediv__
¶Vector division is just scalar division, so we’re going to write a __truediv__
method that takes self
as the first argument and other
as the second argument, and returns a new vector the same size as self
with every element divided by other
. For an extra challenge, try writing this one in one line using assignment function notation.
Tests:
vector(3, 4) / 1 |> print # vector(*pts=(3.0, 4.0))
vector(2, 4) / 2 |> print # vector(*pts=(1.0, 2.0))
Hint: Look back at how we implemented scalar multiplication.
Here’s my solution for you to check against:
def __truediv__(self, other) = self.pts |> map$((x) -> x/other) |*> vector
.unit
¶Next up, .unit
. We’re going to write a unit
method that takes just self
as its argument and returns a new vector the same size as self
with each element divided by the magnitude of self
, which we can retrieve with abs
. This should be a very simple one-line function.
Tests:
vector(0, 1).unit() |> print # vector(*pts=(0.0, 1.0))
vector(5, 0).unit() |> print # vector(*pts=(1.0, 0.0))
Here’s my solution:
def unit(self) = self / abs(self)
.angle
¶This one is going to be a little bit more complicated. For starters, the mathematical formula for the angle between two vectors is the math.acos
of the dot product of those vectors’ respective unit vectors, and recall that we already implemented the dot product of two vectors when we wrote __mul__
. So, .angle
should take self
as the first argument and other
as the second argument, and if other
is a vector, use that formula to compute the angle between self
and other
, or if other
is not a vector, .angle
should raise a MatchError
. To accomplish this, we’re going to want to use destructuring assignment to check that other
is indeed a vector
.
Tests:
import math
vector(2, 0).angle(vector(3, 0)) |> print # 0.0
print(vector(1, 0).angle(vector(0, 2)), math.pi/2) # should be the same
vector(1, 2).angle(5) # MatchError
Hint: Look back at how we checked whether the argument to factorial
was an integer using destructuring assignment.
Here’s my solution—take a look:
def angle(self, other is vector) = math.acos(self.unit() * other.unit())
And now it’s time to put it all together. Feel free to substitute in your own versions of the methods we just defined.
import math # necessary for math.acos in .angle
data vector(*pts):
"""Immutable n-vector."""
def __new__(cls, *pts):
"""Create a new vector from the given pts."""
match (v is vector,) in pts:
return v # vector(v) where v is a vector should return v
else:
return pts |*> datamaker(cls) # accesses base constructor
def __abs__(self) =
"""Return the magnitude of the vector."""
self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5)
def __add__(self, other) =
"""Add two vectors together."""
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((+), self.pts, other_pts) |*> vector
def __sub__(self, other) =
"""Subtract one vector from another."""
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((-), self.pts, other_pts) |*> vector
def __neg__(self) =
"""Retrieve the negative of the vector."""
self.pts |> map$((-)) |*> vector
def __eq__(self, other):
"""Compare whether two vectors are equal."""
match vector(*=self.pts) in other:
return True
else:
return False
def __mul__(self, other):
"""Scalar multiplication and dot product."""
match vector(*other_pts) in other:
assert len(other_pts) == len(self.pts)
return map((*), self.pts, other_pts) |> sum # dot product
else:
return self.pts |> map$((*)$(other)) |*> vector # scalar multiplication
def __rmul__(self, other) =
"""Necessary to make scalar multiplication commutative."""
self * other
# New one-line functions necessary for finding the angle between vectors:
def __truediv__(self, other) = self.pts |> map$((x) -> x/other) |*> vector
def unit(self) = self / abs(self)
def angle(self, other is vector) = math.acos(self.unit() * other.unit())
# Test cases:
vector(3, 4) / 1 |> print # vector(*pts=(3.0, 4.0))
vector(2, 4) / 2 |> print # vector(*pts=(1.0, 2.0))
vector(0, 1).unit() |> print # vector(*pts=(0.0, 1.0))
vector(5, 0).unit() |> print # vector(*pts=(1.0, 0.0))
vector(2, 0).angle(vector(3, 0)) |> print # 0.0
print(vector(1, 0).angle(vector(0, 2)), math.pi/2) # should be the same
vector(1, 2).angle(5) # MatchError
One note of warning here: be careful not to leave a blank line when substituting in your methods, or the interpreter will cut off the code for the vector
there. This isn’t a problem in normal Coconut code, only here because we’re copy-and-pasting into the command line.
Copy, paste! If everything is working, I’d recommend going back to playing around with vector_field
applications using our new methods.
And with that, this tutorial is out of case studies—but that doesn’t mean Coconut is out of features! In this last section, we’ll touch on three of the most important features of Coconut that we managed to miss in our case studies: lazy lists, function composition, and implicit partials.
First up is lazy lists. Lazy lists are lazily-evaluated iterator literals, similar in their laziness to Coconut’s ::
operator, in that any expressions put inside a lazy list won’t be evaluated until that element of the lazy list is needed. The syntax for lazy lists is exactly the same as the syntax for normal lists, but with “banana brackets” ((|
and |)
) instead of normal brackets, like so:
abc = (| a, b, c |)
Next is function composition. In Coconut, this is accomplished through the ..
operator, which takes two functions and composes them, creating a new function equivalent to (*args, **kwargs) -> f1(f2(*args, **kwargs))
. This can be useful in combination with partial application for piecing together multiple higher-order functions, like so:
zipsum = map$(sum)..zip
Function composition also gets rid of the need for lots of parentheses when chaining function calls, like so:
(plus1..square)(3) == 10
Last is implicit partials. Coconut supports a number of different “incomplete” expressions that will evaluate to a function that takes in the part necessary to complete them, that is, an implicit partial application function. The different allowable expressions are:
.attr
.method(args)
obj.
func$
seq[]
iter$[]
.[slice]
.$[slice]
And that’s it for this tutorial! But that’s hardly it for Coconut. All of the features examined in this tutorial, as well as a bunch of others, are documented in detail in Coconut’s comprehensive documentation.
Also, if you have any other questions not covered in this tutorial, feel free to ask around at Coconut’s Gitter, a GitHub-integrated chat room for Coconut developers.
Finally, Coconut is a new, growing language, and if you’d like to get involved in the development of Coconut, all the code is available completely open-source on Coconut’s GitHub. Contributing is a simple as forking the code, making your changes, and proposing a pull request.
This documentation covers all the features of the Coconut Programming Language, and is intended as a reference/specification, not a tutorialized introduction. For a full introduction and tutorial of Coconut, see the tutorial.
Coconut is a variant of Python built for simple, elegant, Pythonic functional programming. Coconut syntax is a strict superset of Python 3 syntax. Thus, users familiar with Python will already be familiar with most of Coconut.
The Coconut compiler turns Coconut code into Python code. The primary method of accessing the Coconut compiler is through the Coconut command-line utility, which also features an interpreter for real-time compilation. In addition to the command-line utility, Coconut also supports the use of IPython/Jupyter notebooks.
While most of Coconut gets its inspiration simply from trying to make functional programming work in Python, additional inspiration came from Haskell, CoffeeScript, F#, and patterns.py.
Since Coconut is hosted on the Python Package Index, it can be installed easily using pip
. Simply install Python, open up a command-line prompt, and enter
pip install coconut
which will install Coconut and its required dependencies. Coconut also has some optional dependencies, which can be installed by entering
pip install coconut[all]
which will enable the use of Coconut’s --jobs
, --watch
, --jupyter
, and --mypy
flags. To install the optional dependencies only for a particular flag, simply put the flag name in place of all
.
Alternatively, if you want to test out Coconut’s latest and greatest, enter
pip install coconut-develop
which will install the most recent working development build (optional dependency installation is also supported in the same manner as above if you want). For more information on the current development build, check out the development version of this documentation. Be warned: coconut-develop
is likely to be unstable—if you find a bug, please report it by creating a new issue.
coconut [-h] [-v] [-t version] [-i] [-p] [-a] [-l] [-k] [-w] [-r] [-n]
[-d] [-q] [-s] [--no-tco] [-c code] [-j processes] [-f]
[--minify] [--jupyter ...] [--mypy ...] [--tutorial]
[--documentation] [--style name] [--recursion-limit limit]
[--verbose] [--trace]
[source] [dest]
source path to the Coconut file/folder to compile
dest destination directory for compiled files (defaults to
the source directory)
-h, --help show this help message and exit
-v, --version print Coconut and Python version information
-t version, --target version
specify target Python version (defaults to universal)
-i, --interact force the interpreter to start (otherwise starts if no
other command is given) (implies --run)
-p, --package compile source as part of a package (defaults to only
if source is a directory)
-a, --standalone compile source as standalone files (defaults to only
if source is a single file)
-l, --line-numbers, --linenumbers
add line number comments for ease of debugging
-k, --keep-lines, --keeplines
include source code in comments for ease of debugging
-w, --watch watch a directory and recompile on changes
-r, --run execute compiled Python
-n, --no-write, --nowrite
disable writing compiled Python
-d, --display print compiled Python
-q, --quiet suppress all informational output (combine with
--display to write runnable code to stdout)
-s, --strict enforce code cleanliness standards
--no-tco, --notco disable tail call optimization for ease of debugging
-c code, --code code run Coconut passed in as a string (can also be piped
into stdin)
-j processes, --jobs processes
number of additional processes to use (defaults to 0)
(pass 'sys' to use machine default)
-f, --force force overwriting of compiled Python (otherwise only
overwrites when source code or compilation parameters
change)
--minify reduce size of compiled Python
--jupyter ..., --ipython ...
run Jupyter/IPython with Coconut as the kernel
(remaining args passed to Jupyter)
--mypy ... run MyPy on compiled Python (remaining args passed to
MyPy) (implies --package --no-tco)
--tutorial open the Coconut tutorial in the default web browser
--documentation open the Coconut documentation in the default web
browser
--style name Pygments syntax highlighting style (or 'none' to
disable) (defaults to COCONUT_STYLE environment
variable, if it exists, otherwise 'default')
--recursion-limit limit, --recursionlimit limit
set maximum recursion depth in compiler (defaults to
2000)
--verbose print verbose debug output
--trace show verbose parsing data (only available in coconut-
develop)
To run a Coconut file as a script, Coconut provides the command coconut-run
as an alias for coconut --run --quiet
that also passes all additional command-line arguments to the script being run. coconut-run
will quietly compile the file if it’s been changed or use the existing compiled Python if it hasn’t and then run that. coconut-run
can be used in a Unix shebang to create a Coconut script with the following line:
#!/usr/bin/env coconut-run
Coconut source files should, so the compiler can recognize them, use the extension .coco
(preferred), .coc
, or .coconut
. When Coconut compiles a .coco
(or .coc
/ .coconut
) file, it will compile to another file with the same name, except with .py
instead of .coco
, which will hold the compiled code. If an extension other than .py
is desired for the compiled files, such as .pyde
for Python Processing, then that extension can be put before .coco
in the source file name, and it will be used instead of .py
for the compiled files. For example, name.coco
will compile to name.py
, whereas name.pyde.coco
will compile to name.pyde
.
Files compiled by the coconut
command-line utility will vary based on compilation parameters. If an entire directory of files is compiled (which the compiler will search recursively for any folders containing .coco
, .coc
, or .coconut
files), a __coconut__.py
file will be created to house necessary functions (package mode), whereas if only a single file is compiled, that information will be stored within a header inside the file (standalone mode). Standalone mode is better for single files because it gets rid of the overhead involved in importing __coconut__.py
, but package mode is better for large packages because it gets rid of the need to run the same Coconut header code again in every file, since it can just be imported from __coconut__.py
.
By default, if the source
argument to the command-line utility is a file, it will perform standalone compilation on it, whereas if it is a directory, it will recursively search for all .coco
(or .coc
/ .coconut
) files and perform package compilation on them. Thus, in most cases, the mode chosen by Coconut automatically will be the right one. But if it is very important that no additional files like __coconut__.py
be created, for example, then the command-line utility can also be forced to use a specific mode with the --package
(-p
) and --standalone
(-a
) flags.
While Coconut syntax is based off of Python 3, Coconut code compiled in universal mode (the default --target
), and the Coconut compiler, should run on any Python version >= 2.6
on the 2.x
branch or >= 3.2
on the 3.x
branch.
Note: The tested against implementations are CPython 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6
and PyPy 2.7, 3.2
.
As part of Coconut’s cross-compatibility efforts, Coconut adds in new Python 3 built-ins and overwrites Python 2 built-ins to use the Python 3 versions where possible. Additionally, Coconut also overrides some Python 3 built-ins for optimization purposes. If access to the Python versions is desired, the old built-ins can be retrieved by prefixing them with py_
.
For standard library compatibility, Coconut automatically maps imports under Python 3 names to imports under Python 2 names. Thus, Coconut will automatically take care of any standard library modules that were renamed from Python 2 to Python 3 if just the Python 3 name is used. For modules or objects that only exist in Python 3, however, Coconut has no way of maintaining compatibility.
Finally, while Coconut will try to compile Python-3-specific syntax to its universal equivalent, the following constructs have no equivalent in Python 2, and require the specification of a target of at least 3
to be used:
*
s (use Coconut pattern-matching instead),nonlocal
keyword,exec
used in a context where it must be a function,*
unpacking or dicts with **
unpacking (requires --target 3.5
),@
as matrix multiplication (requires --target 3.5
),async
and await
statements (requires --target 3.5
), andf
(requires --target 3.6
).If the version of Python that the compiled code will be running on is known ahead of time, a target should be specified with --target
. The given target will only affect the compiled code and whether or not the Python-3-specific syntax detailed above is allowed. Where Python 3 and Python 2 syntax standards differ, Coconut syntax will always follow Python 3 across all targets. The supported targets are:
2
, 26
(will work on any Python >= 2.6
but < 3
),27
(will work on any Python >= 2.7
but < 3
),3
, 32
(will work on any Python >= 3.2
),33
, 34
(will work on any Python >= 3.3
),35
(will work on any Python >= 3.5
),36
(will work on any Python >= 3.6
),sys
(chooses the specific target corresponding to the current version).Note: Periods are ignored in target specifications, such that the target 2.7
is equivalent to the target 27
.
strict
Mode¶If the --strict
(or -s
) flag is enabled, Coconut will throw errors on various style problems. These are
--strict
will show a Warning),--strict
will show a Warning),from __future__
imports (without --strict
will show a Warning)lambda
statement,u
to denote Unicode strings, andIt is recommended that you use the --strict
(or -s
) flag if you are starting a new Coconut project, as it will help you write cleaner code.
The current options for Coconut syntax highlighting are:
coconut.vim
, a third-party Vim highlighter,coconut-mode
, a third-party Emacs highlighter, orInstructions on how to set up syntax highlighting for SublimeText and Pygments are included below. If one of the actual highlighters above doesn’t work, however, it should be sufficient to set up your editor so it interprets all .coco
(also .coc
and .coconut
, although .coco
is the preferred extension) files as Python code, as this should highlight most of your code well enough.
Coconut syntax highlighting for SublimeText requires that Package Control, the standard package manager for SublimeText, be installed. Once that is done, simply:
Ctrl+Shift+P
,Package Control: Install Package
, andCoconut
.To make sure everything is working properly, open a .coco
file, and make sure Coconut
appears in the bottom right-hand corner. If something else appears, like Plain Text
, click on it, select Open all with current extension as...
at the top of the resulting menu, and then select Coconut
.
The same pip install coconut
command that installs the Coconut command-line utility will also install the coconut
Pygments lexer. How to use this lexer depends on the Pygments-enabled application being used, but in general simply enter coconut
as the language being highlighted and/or use a valid Coconut file extension (.coco
, .coc
, or .coconut
) and Pygments should be able to figure it out. For example, this documentation is generated with Sphinx, with the syntax highlighting you see created by adding the line
highlight_language = "coconut"
to Coconut’s conf.py
.
If you prefer IPython (the python kernel for the Jupyter framework) to the normal Python shell, Coconut can be used as a Jupyter kernel or IPython extension.
If Coconut is used as a kernel, all code in the console or notebook will be sent directly to Coconut instead of Python to be evaluated. Otherwise, the Coconut kernel behaves exactly like the IPython kernel, including support for %magic
commands.
The command coconut --jupyter notebook
(or coconut --ipython notebook
) will launch an IPython/Jupyter notebook using Coconut as the kernel and the command coconut --jupyter console
(or coconut --ipython console
) will launch an IPython/Jupyter console using Coconut as the kernel. Additionally, the command coconut --jupyter
(or coconut --ipython
) will add Coconut as a language option inside of all IPython/Jupyter notebooks, even those not launched with Coconut. This command may need to be re-run when a new version of Coconut is installed.
If Coconut is used as an extension, a special magic command will send snippets of code to be evaluated using Coconut instead of IPython, but IPython will still be used as the default.
The line magic %load_ext coconut
will load Coconut as an extension, providing the %coconut
and %%coconut
magics and adding Coconut built-ins. The %coconut
line magic will run a line of Coconut with default parameters, and the %%coconut
block magic will take command-line arguments on the first line, and run any Coconut code provided in the rest of the cell with those parameters.
Coconut has the ability to integrate with MyPy to provide optional static type-checking, including for all Coconut built-ins.
Simply pass --mypy
(be careful to pass it only as the last argument), use standard Python 3 type annotation syntax, and Coconut will take care of the rest. By default, Coconut compiles Python 3 type annotations into mypy --py2
compatible type comments. If you want to keep the Python 3 type annotations instead, simply pass --target 3
.
In addition to function argument type annotation, Coconut also supports variable type annotation using the new Python 3.6 syntax, which compiles to mypy --py2
compatible type comments unless --target 3.6
is specified.
Coconut even supports --mypy
in the interpreter, which will intelligently scan each new line of code, in the context of previous lines, for newly-introduced MyPy errors. For example:
>>> a: str = count()[0]
<string>:14: error: Incompatible types in assignment (expression has type "int", variable has type "str")
Note: Since tail call optimization prevents proper type-checking, --mypy
implicitly disables it.
Coconut provides the simple, clean ->
operator as an alternative to Python’s lambda
statements. The syntax for the ->
operator is (arguments) -> expression
. The operator has the same precedence as the old statement, which means it will often be necessary to surround the lambda in parentheses.
Additionally, Coconut also supports an implicit usage of the ->
operator of the form (-> expression)
, which is equivalent to ((_=None) -> expression)
, which allows an implicit lambda to be used both when no arguments are required, and when one argument (assigned to _
) is required.
Note: If normal lambda syntax is insufficient, Coconut also supports an extended lambda syntax in the form of statement lambdas.
In Python, lambdas are ugly and bulky, requiring the entire word lambda
to be written out every time one is constructed. This is fine if in-line functions are very rarely needed, but in functional programming in-line functions are an essential tool.
Lambda forms (lambda expressions) have the same syntactic position as expressions. They are a shorthand to create anonymous functions; the expression (arguments) -> expression
yields a function object. The unnamed object behaves like a function object defined with:
def <lambda>(arguments):
return expression
Note that functions created with lambda forms cannot contain statements or annotations.
Coconut:
dubsums = map((x, y) -> 2*(x+y), range(0, 10), range(10, 20))
dubsums |> list |> print
Python:
dubsums = map(lambda x, y: 2*(x+y), range(0, 10), range(10, 20))
print(list(dubsums))
Coconut uses a $
sign right after a function’s name but before the open parenthesis used to call the function to denote partial application. It has the same precedence as subscription.
Coconut’s partial application also supports the use of a ?
to skip partially applying an argument, deferring filling in that argument until the partially-applied function is called. This is useful if you want to partially apply argument(s) that aren’t first in the argument order.
Partial application, or currying, is a mainstay of functional programming, and for good reason: it allows the dynamic customization of functions to fit the needs of where they are being used. Partial application allows a new function to be created out of an old function with some of its arguments pre-specified.
Return a new partial
object which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args. If additional keyword arguments are supplied, they extend and override keywords. Roughly equivalent to:
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
The partial
object is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature.
Coconut:
expnums = range(5) |> map$(pow$(?, 2))
expnums |> list |> print
Python:
# unlike this simple lambda, $ produces a pickleable object
expnums = map(lambda x: pow(x, 2), range(5))
print(list(expnums))
Coconut uses pipe operators for pipeline-style function application. All the operators have a precedence in-between infix calls and comparisons and are left-associative. All operators also support in-place versions. The different operators are:
(|>) => pipe forward
(|*>) => multiple-argument pipe forward
(<|) => pipe backward
(<*|) => multiple-argument pipe backward
Coconut:
def sq(x) = x**2
(1, 2) |*> (+) |> sq |> print
Python:
import operator
def sq(x): return x**2
print(sq(operator.add(1, 2)))
Coconut uses the ..
operator for function composition. It has a precedence in-between subscription and exponentiation. The in-place operator is ..=
.
Coconut:
fog = f..g
Python:
# unlike this simple lambda, .. produces a pickleable object
fog = lambda *args, **kwargs: f(g(*args, **kwargs))
Coconut uses the ::
operator for iterator chaining. Coconut’s iterator chaining is done lazily, in that the arguments are not evaluated until they are needed. It has a precedence in-between bitwise or and infix calls. The in-place operator is ::=
.
A useful tool to make working with iterators as easy as working with sequences is the ability to lazily combine multiple iterators together. This operation is called chain, and is equivalent to addition with sequences, except that nothing gets evaluated until it is needed.
Make an iterator that returns elements from the first iterable until it is exhausted, then proceeds to the next iterable, until all of the iterables are exhausted. Used for treating consecutive sequences as a single sequence. Chained inputs are evaluated lazily. Roughly equivalent to:
def chain(*iterables):
# chain('ABC', 'DEF') --> A B C D E F
for it in iterables:
for element in it:
yield element
Coconut:
def N(n=0) = (n,) :: N(n+1) # no infinite loop because :: is lazy
(range(-10, 0) :: N())$[5:15] |> list |> print
Python: Can’t be done without a complicated iterator comprehension in place of the lazy chaining. See the compiled code for the Python syntax.
Coconut uses a $
sign right after an iterator before a slice to perform iterator slicing. Coconut’s iterator slicing works much the same as Python’s sequence slicing, and looks much the same as Coconut’s partial application, but with brackets instead of parentheses. It has the same precedence as subscription.
Iterator slicing works just like sequence slicing, including support for negative indices and slices, and support for slice
objects in the same way as can be done with normal slicing. Iterator slicing makes no guarantee, however, that the original iterator passed to it be preserved (to preserve the iterator, use Coconut’s tee
function).
Coconut’s iterator slicing is very similar to Python’s itertools.islice
, but unlike itertools.islice
, Coconut’s iterator slicing supports negative indices, and will preferentially call an object’s __getitem__
, if it exists. Coconut’s iterator slicing is also optimized to work well with all of Coconut’s built-in objects, only computing the elements of each that are actually necessary to extract the desired slice.
Coconut:
map((x)->x*2, range(10**100))$[-1] |> print
Python: Can’t be done without a complicated iterator slicing function and inspection of custom objects. The necessary definitions in Python can be found in the Coconut header.
Coconut supports Unicode alternatives to many different operator symbols. The Unicode alternatives are relatively straightforward, and chosen to reflect the look and/or meaning of the original symbol.
→ (\u2192) => "->"
↦ (\u21a6) => "|>"
*↦ (*\u21a6) => "|*>"
↤ (\u21a4) => "<|"
↤* (\u21a4*) => "<*|"
⋅ (\u22c5) => "*"
↑ (\u2191) => "**"
÷ (\xf7) => "/"
÷/ (\xf7/) => "//"
∘ (\u2218) => ".."
− (\u2212) => "-" (only subtraction)
⁻ (\u207b) => "-" (only negation)
¬ (\xac) => "~"
≠ (\u2260) or ¬= (\xac=) => "!="
≤ (\u2264) => "<="
≥ (\u2265) => ">="
∧ (\u2227) or ∩ (\u2229) => "&"
∨ (\u2228) or ∪ (\u222a) => "|"
⊻ (\u22bb) or ⊕ (\u2295) => "^"
« (\xab) => "<<"
» (\xbb) => ">>"
… (\u2026) => "..."
× (\xd7) => "@" (only matrix multiplication)
data
¶The syntax for data
blocks is a cross between the syntax for functions and the syntax for classes. The first line looks like a function definition, but the rest of the body looks like a class, usually containing method definitions. This is because while data
blocks actually end up as classes in Python, Coconut automatically creates a special, immutable constructor based on the given arguments.
Coconut data
blocks create immutable classes derived from collections.namedtuple
and made immutable with __slots__
. Coconut data statement syntax looks like:
data <name>(<args>) [from <inherits>]:
<body>
<name>
is the name of the new data type, <args>
are the arguments to its constructor as well as the names of its attributes, <body>
contains the data type’s methods, and <inherits>
optionally contains any desired base classes.
In addition to supporting standard collections.namedtuple
subclassing when <args>
is a list of names, Coconut also supports an extended version where <args>
can contain a starred argument to collect extra parameters.
Subclassing data
types can be done easily by inheriting from them either in another data
statement or a normal Python class
. If a normal class
statement is used, making the new subclass immutable will require adding the line
__slots__ = ()
which will need to be put in the subclass body before any method or attribute definitions.
A mainstay of functional programming that Coconut improves in Python is the use of values, or immutable data types. Immutable data can be very useful because it guarantees that once you have some data it won’t change, but in Python creating custom immutable data types is difficult. Coconut makes it very easy by providing data
blocks.
Returns a new tuple subclass. The new subclass is used to create tuple-like objects that have fields accessible by attribute lookup as well as being indexable and iterable. Instances of the subclass also have a helpful docstring (with type names and field names) and a helpful __repr__()
method which lists the tuple contents in a name=value
format.
Any valid Python identifier may be used for a field name except for names starting with an underscore. Valid identifiers consist of letters, digits, and underscores but do not start with a digit or underscore and cannot be a keyword such as class, for, return, global, pass, or raise.
Named tuple instances do not have per-instance dictionaries, so they are lightweight and require no more memory than regular tuples.
Coconut:
data vector2(x, y):
def __abs__(self):
return (self.x**2 + self.y**2)**.5
v = vector2(3, 4)
v |> print # all data types come with a built-in __repr__
v |> abs |> print
v.x = 2 # this will fail because data objects are immutable
Showcases the syntax, features, and immutable nature of data
types.
data Empty()
data Leaf(n)
data Node(l, r)
def size(Empty()) = 0
@addpattern(size)
def size(Leaf(n)) = 1
@addpattern(size)
def size(Node(l, r)) = size(l) + size(r)
size(Node(Empty(), Leaf(10))) == 1
Showcases the algebraic nature of data
types when combined with pattern-matching.
data vector(*pts):
"""Immutable arbitrary-length vector."""
def __abs__(self) =
self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5)
def __add__(self, other) =
vector(*other_pts) = other
assert len(other_pts) == len(self.pts)
map((+), self.pts, other_pts) |*> vector
def __neg__(self) =
self.pts |> map$((-)) |*> vector
def __sub__(self, other) =
self + -other
Showcases starred data
declaration.
Python:
import collections
class vector2(collections.namedtuple("vector2", "x, y")):
__slots__ = ()
def __abs__(self):
return (self.x**2 + self.y**2)**.5
v = vector2(3, 4)
print(v)
print(abs(v))
v.x = 2
import collections
class Empty(collections.namedtuple("Empty", "")):
__slots__ = ()
class Leaf(collections.namedtuple("Leaf", "n")):
__slots__ = ()
class Node(collections.namedtuple("Node", "l, r")):
__slots__ = ()
def size(tree):
if isinstance(tree, Empty):
return 0
elif isinstance(tree, Leaf):
return 1
elif isinstance(tree, Node):
return size(tree[0]) + size(tree[1])
else:
raise MatchError()
size(Node(Empty(), Leaf(10))) == 1
Starred data declarations can’t be done without a long sequence of method definitions. See the compiled code for the Python syntax.
match
¶Coconut provides fully-featured, functional pattern-matching through its match
statements.
Match statements follow the basic syntax match <pattern> in <value>
. The match statement will attempt to match the value against the pattern, and if successful, bind any variables in the pattern to whatever is in the same position in the value, and execute the code below the match statement. Match statements also support, in their basic syntax, an if <cond>
that will check the condition after executing the match before executing the code below, and an else
statement afterwards that will only be executed if the match
statement is not. What is allowed in the match statement’s pattern has no equivalent in Python, and thus the specifications below are provided to explain it.
Coconut match statement syntax is
match <pattern> in <value> [if <cond>]:
<body>
[else:
<body>]
where <value>
is the item to match against, <cond>
is an optional additional check, and <body>
is simply code that is executed if the header above it succeeds. <pattern>
follows its own, special syntax, defined roughly like so:
pattern ::= (
"(" pattern ")" # parentheses
| "None" | "True" | "False" # constants
| "=" NAME # check
| NUMBER # numbers
| STRING # strings
| [pattern "as"] NAME # capture
| NAME "(" patterns ")" # data types
| "(" patterns ")" # sequences can be in tuple form
| "[" patterns "]" # or in list form
| "(|" patterns "|)" # lazy lists
| "{" pattern_pairs "}" # dictionaries
| ["s"] "{" pattern_consts "}" # sets
| ("(" | "[") # star splits
patterns,
"*" middle,
patterns
(")" | "]") # must both be parens or brackets
| ( # head-tail splits
"(" patterns ")"
| "[" patterns "]"
) "+" pattern
| pattern "+" ( # init-last splits
"(" patterns ")"
| "[" patterns "]"
)
| ( # head-last splits
"(" patterns ")"
| "[" patterns "]"
) "+" pattern "+" (
"(" patterns ")" # this match must be the same
| "[" patterns "]" # construct as the first match
)
| ( # iterator splits
"(" patterns ")"
| "[" patterns "]"
| "(|" patterns "|)"
) "::" pattern
| pattern "is" exprs # type-checking
| pattern "and" pattern # match all
| pattern "or" pattern # match any
)
match
statements will take their pattern and attempt to “match” against it, performing the checks and deconstructions on the arguments as specified by the pattern. The different constructs that can be specified in a pattern, and their function, are:
_
is used, nothing will be bound and everything will always match to it.<pattern> as <var>
): will bind <var>
to <pattern>
.=<var>
): will check that whatever is in that position is equal to the previously defined variable <var>
.<var> is <types>
): will check that whatever is in that position is of type(s) <types>
before binding the <var>
.<name>(<args>)
): will check that whatever is in that position is of data type <name>
and will match the attributes to <args>
.[<patterns>]
), Tuples ((<patterns>)
): will only match a sequence (collections.abc.Sequence
) of the same length, and will check the contents against <patterns>
.(|<patterns>|)
): same as list or tuple matching, but checks iterable (collections.abc.Iterable
) instead of sequence.{<pairs>}
): will only match a mapping (collections.abc.Mapping
) of the same length, and will check the contents against <pairs>
.{<constants>}
): will only match a set (collections.abc.Set
) of the same length and contents.<list/tuple> + <var>
): will match the beginning of the sequence against the <list/tuple>
, then bind the rest to <var>
, and make it the type of the construct used.<var> + <list/tuple>
): exactly the same as head-tail splits, but on the end instead of the beginning of the sequence.<list/tuple> + <var> + <list/tuple>
): the combination of a head-tail and an init-last split.<list/tuple/lazy list> :: <var>
): will match the beginning of an iterable (collections.abc.Iterable
) against the <list/tuple/lazy list>
, then bind the rest to <var>
or check that the iterable is done.Note: Like iterator slicing, iterator and lazy list matching make no guarantee that the original iterator matched against be preserved (to preserve the iterator, use Coconut’s tee
function.
When checking whether or not an object can be matched against in a particular fashion, Coconut makes use of Python’s abstract base classes. Therefore, to enable proper matching for a custom object, register it with the proper abstract base classes.
Coconut:
def factorial(value):
match 0 in value:
return 1
else: match n is int in value if n > 0: # possible because of Coconut's
return n * factorial(n-1) # enhanced else statements
else:
raise TypeError("invalid argument to factorial of: "+repr(value))
3 |> factorial |> print
Showcases else
statements, which work much like else
statements in Python: the code under an else
statement is only executed if the corresponding match fails.
data point(x, y):
def transform(self, other):
match point(x, y) in other:
return point(self.x + x, self.y + y)
else:
raise TypeError("arg to transform must be a point")
def __eq__(self, other):
match point(=self.x, =self.y) in other:
return True
else:
return False
point(1,2) |> point(3,4).transform |> print
point(1,2) |> (==)$(point(1,2)) |> print
Showcases matching to data types. Values defined by the user with the data
statement can be matched against and their contents accessed by specifically referencing arguments to the data type’s constructor.
data Empty()
data Leaf(n)
data Node(l, r)
Tree = (Empty, Leaf, Node) # type union
def depth(Tree()) = 0
@addpattern(depth)
def depth(Tree(n)) = 1
@addpattern(depth)
def depth(Tree(l, r)) = 1 + max([depth(l), depth(r)])
Empty() |> depth |> print
Leaf(5) |> depth |> print
Node(Leaf(2), Node(Empty(), Leaf(3))) |> depth |> print
Showcases how the combination of data types and match statements can be used to powerful effect to replicate the usage of algebraic data types in other functional programming languages.
def duplicate_first([x] + xs as l) =
[x] + l
[1,2,3] |> duplicate_first |> print
Showcases head-tail splitting, one of the most common uses of pattern-matching, where a + <var>
(or :: <var>
for any iterable) at the end of a list or tuple literal can be used to match the rest of the sequence.
def sieve([head] :: tail) =
[head] :: sieve(n for n in tail if n % head)
@addpattern(sieve)
def sieve((||)) = []
Showcases how to match against iterators, namely that the empty iterator case ((||)
) must come last, otherwise that case will exhaust the whole iterator before any other pattern has a chance to match against it.
Python:
Can’t be done without a long series of checks for each match
statement. See the compiled code for the Python syntax.
case
¶Coconut’s case
statement is an extension of Coconut’s match
statement for performing multiple match
statements against the same value, where only one of them should succeed. Unlike lone match
statements, only one match statement inside of a case
block will ever succeed, and thus more general matches should be put below more specific ones.
Each pattern in a case block is checked until a match is found, and then the corresponding body is executed, and the case block terminated. The syntax for case blocks is
case <value>:
match <pattern> [if <cond>]:
<body>
match <pattern> [if <cond>]:
<body>
...
[else:
<body>]
where <pattern>
is any match
pattern, <value>
is the item to match against, <cond>
is an optional additional check, and <body>
is simply code that is executed if the header above it succeeds. Note the absence of an in
in the match
statements: that’s because the <value>
in case <value>
is taking its place.
Coconut:
def classify_sequence(value):
out = "" # unlike with normal matches, only one of the patterns
case value: # will match, and out will only get appended to once
match ():
out += "empty"
match (_,):
out += "singleton"
match (x,x):
out += "duplicate pair of "+str(x)
match (_,_):
out += "pair"
match _ is (tuple, list):
out += "sequence"
else:
raise TypeError()
return out
[] |> classify_sequence |> print
() |> classify_sequence |> print
[1] |> classify_sequence |> print
(1,1) |> classify_sequence |> print
(1,2) |> classify_sequence |> print
(1,1,1) |> classify_sequence |> print
Python:
Can’t be done without a long series of checks for each match
statement. See the compiled code for the Python syntax.
In Coconut, the keywords data
, match
, case
, async
(keyword in Python 3.5), and await
(keyword in Python 3.5) are also valid variable names. While Coconut can disambiguate these two use cases, when using one of these keywords as a variable name, a backslash is allowed in front to be explicit about using a keyword as a variable name.
It is illegal for a variable name to start with _coconut
, as these variables are reserved for the compiler. If interacting with such variables is necessary, use code passthrough.
The statement lambda syntax is an extension of the normal lambda syntax to support statements, not just expressions.
The syntax for a statement lambda is:
def (arguments) -> statement; statement; ...
where arguments
can be standard function arguments or pattern-matching function definition arguments and statement
can be an assignment statement or a keyword statement. If the last statement
(not followed by a semicolon) is an expression
, it will automatically be returned.
Statement lambdas also support implicit lambda syntax, where when the arguments are omitted, as in def -> _
, def (_=None) -> _
is assumed.
Coconut:
L |> map$(def (x) -> y = 1 / x; y*(1 - y))
Python:
def _lambda(x):
y = 1 / x
return y*(1 - y)
map(_lambda, L)
Coconut supports the creation of lazy lists, where the contents in the list will be treated as an iterator and not evaluated until they are needed. Lazy lists can be created in Coconut simply by simply surrounding a comma-seperated list of items with (|
and |)
(so-called “banana brackets”) instead of [
and ]
for a list or (
and )
for a tuple.
Lazy lists use the same machinery as iterator chaining to make themselves lazy, and thus the lazy list (| x, y |)
is equivalent to the iterator chaining expression (x,) :: (y,)
, although the lazy list won’t construct the intermediate tuples.
Lazy lists, where sequences are only evaluated when their contents are requested, are a mainstay of functional programming, allowing for dynamic evaluation of the list’s contents.
Coconut:
(| print("hello,"), print("world!") |) |> consume
Python: Can’t be done without a complicated iterator comprehension in place of the lazy list. See the compiled code for the Python syntax.
Coconut supports a number of different syntactical aliases for common partial application use cases. These are:
.attr => operator.attrgetter("attr")
.method(args) => operator.methodcaller("method", args)
obj. => getattr$(obj)
func$ => ($)$(func)
seq[] => operator.getitem$(seq)
iter$[] => # the equivalent of seq[] for iterators
.[a:b:c] => operator.itemgetter(slice(a, b, c))
.$[a:b:c] => # the equivalent of .[a:b:c] for iterators
Coconut allows an optional s
to be prepended in front of Python set literals. While in most cases this does nothing, in the case of the empty set it lets Coconut know that it is an empty set and not an empty dictionary. Additionally, an f
is also supported, in which case a Python frozenset
will be generated instead of a normal set.
In addition to Python’s <num>j
or <num>J
notation for imaginary literals, Coconut also supports <num>i
or <num>I
, to make imaginary literals more readable if used in a mathematical context.
Imaginary literals are described by the following lexical definitions:
imagnumber ::= (floatnumber | intpart) ("j" | "J" | "i" | "I")
An imaginary literal yields a complex number with a real part of 0.0. Complex numbers are represented as a pair of floating point numbers and have the same restrictions on their range. To create a complex number with a nonzero real part, add a floating point number to it, e.g., (3+4i)
. Some examples of imaginary literals:
3.14i 10.i 10i .001i 1e100i 3.14e-10i
Coconut allows for one underscore between digits and after base specifiers in numeric literals as specified in PEP 515. These underscores are ignored and should only be used to increase code readability.
Coconut will perform automatic tail call optimization and tail recursion elimination on any function that meets the following criteria:
return
or assignment function notation) a call to itself (tail recursion elimination, the most powerful optimization) or another function (tail call optimization).yield
) or an asynchronous function (uses async
).Note: Tail call optimization (though not tail recursion elimination) will work even for 1) mutual recursion and 2) pattern-matching functions split across multiple definitions using addpattern
or prepattern
.
If you are encountering a RuntimeError
due to maximum recursion depth, it is highly recommended that you rewrite your function to meet either the criteria above for tail call optimization, or the corresponding criteria for recursive_iterator
, either of which should prevent such errors.
Note: Tail call optimization and tail recursion elimination can be turned off by passing the --no-tco
command-line option.
Coconut:
# unlike in Python, this function will never hit a maximum recursion depth error
def factorial(n, acc=1):
case n:
match 0:
return acc
match _ is int if n > 0:
return factorial(n-1, acc*n)
Showcases tail recursion elimination.
# unlike in Python, neither of these functions will ever hit a maximum recursion depth error
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)
Showcases tail call optimization.
Python: Can’t be done without rewriting the function(s).
Coconut uses a simple operator function short-hand: surround an operator with parentheses to retrieve its function. Similarly to iterator comprehensions, if the operator function is the only argument to a function, the parentheses of the function call can also serve as the parentheses for the operator function.
A very common thing to do in functional programming is to make use of function versions of built-in operators: currying them, composing them, and piping them. To make this easy, Coconut provides a short-hand syntax to access operator functions.
(|>) => # pipe forward
(|*>) => # multi-arg pipe forward
(<|) => # pipe backward
(<*|) => # multi-arg pipe backward
(..) => # function composition
(.) => (getattr)
(::) => (itertools.chain) # will not evaluate its arguments lazily
($) => (functools.partial)
(+) => (operator.add)
(-) => # 1 arg: operator.neg, 2 args: operator.sub
(*) => (operator.mul)
(**) => (operator.pow)
(/) => (operator.truediv)
(//) => (operator.floordiv)
(%) => (operator.mod)
(&) => (operator.and_)
(^) => (operator.xor)
(|) => (operator.or_)
(<<) => (operator.lshift)
(>>) => (operator.rshift)
(<) => (operator.lt)
(>) => (operator.gt)
(==) => (operator.eq)
(<=) => (operator.le)
(>=) => (operator.ge)
(!=) => (operator.ne)
(~) => (operator.inv)
(@) => (operator.matmul)
(not) => (operator.not_)
(and) => # boolean and
(or) => # boolean or
(is) => (operator.is_)
(in) => (operator.contains)
Coconut:
(range(0, 5), range(5, 10)) |*> map$(+) |> list |> print
Python:
import operator
print(list(map(operator.add, range(0, 5), range(5, 10))))
Coconut allows for assignment function definition that automatically returns the last line of the function body. An assignment function is constructed by substituting =
for :
after the function definition line. Thus, the syntax for assignment function definition is either
def <name>(<args>) = <expr>
for one-liners or
def <name>(<args>) =
<stmts>
<expr>
for full functions, where <name>
is the name of the function, <args>
are the functions arguments, <stmts>
are any statements that the function should execute, and <expr>
is the value that the function should return.
Note: Assignment function definition can be combined with infix and/or pattern-matching function definition.
Coconut’s Assignment function definition is as easy to write as assignment to a lambda, but will appear named in tracebacks, as it compiles to normal Python function definition.
Coconut:
def binexp(x) = 2**x
5 |> binexp |> print
Python:
def binexp(x): return 2**x
print(binexp(5))
Coconut supports pattern-matching on the arguments to a function in that function’s definition. The syntax for pattern-matching function definition is
[match] def <name>(<pattern> [= <default>], ... [if <cond>]):
<body>
where <name>
is the name of the function, <cond>
is an optional additional check, <body>
is the body of the function, <pattern>
is defined by Coconut’s match
statement, and <default>
is the optional default if no argument is passed. The match
keyword at the beginning is optional, but is sometimes necessary to disambiguate pattern-matching function definition from normal function definition, which will always take precedence.
If <pattern>
has a variable name (either directly or with as
), the resulting pattern-matching function will support keyword arguments using that variable name. If pattern-matching function definition fails, it will raise a MatchError
object just like destructuring assignment.
Note: Pattern-matching function definition can be combined with assignment and/or infix function definition.
Coconut:
def last_two(_ + [a, b]):
return a, b
def xydict_to_xytuple({"x":x is int, "y":y is int}):
return x, y
range(5) |> last_two |> print
{"x":1, "y":2} |> xydict_to_xytuple |> print
Python: Can’t be done without a long series of checks at the top of the function. See the compiled code for the Python syntax.
Coconut allows for infix function calling, where a function is surrounded by backticks and then can have arguments placed in front of or behind it. Backtick calling has a precedence in-between chaining and piping.
Coconut also supports infix function definition to make defining functions that are intended for infix usage simpler. The syntax for infix function definition is
def <arg> `<name>` <arg>:
<body>
where <name>
is the name of the function, the <arg>
s are the function arguments, and <body>
is the body of the function. If an <arg>
includes a default, the <arg>
must be surrounded in parentheses.
Note: Infix function definition can be combined with assignment and/or pattern-matching function definition.
A common idiom in functional programming is to write functions that are intended to behave somewhat like operators, and to call and define them by placing them between their arguments. Coconut’s infix syntax makes this possible.
Coconut:
def a `mod` b = a % b
(x `mod` 2) `print`
Python:
def mod(a, b): return a % b
print(mod(x, 2))
Coconut allows for function definition using a dotted name to assign a function as a method of an object as specified in PEP 542.
Coconut:
def MyClass.my_method(self):
...
Python:
def my_method(self):
...
MyClass.my_method = my_method
Coconut supports significantly enhanced destructuring assignment, similar to Python’s tuple/list destructuring, but much more powerful. The syntax for Coconut’s destructuring assignment is
[match] <pattern> = <value>
where <value>
is any expression and <pattern>
is defined by Coconut’s match
statement. The match
keyword at the beginning is optional, but is sometimes necessary to disambiguate destructuring assignment from normal assignment, which will always take precedence. Coconut’s destructuring assignment is equivalent to a match statement that follows the syntax:
match <pattern> in <value>:
pass
else:
err = MatchError(<error message>)
err.pattern = "<pattern>"
err.value = <value>
raise err
If a destructuring assignment statement fails, then instead of continuing on as if a match
block had failed, a MatchError
object will be raised describing the failure.
Coconut:
_ + [a, b] = [0, 1, 2, 3]
print(a, b)
Python: Can’t be done without a long series of checks in place of the destructuring assignment statement. See the compiled code for the Python syntax.
Unlike Python, which only supports a single variable or function call in a decorator, Coconut supports any expression.
Coconut:
@ wrapper1 .. wrapper2 $(arg)
def func(x) = x**2
Python:
def wrapper(func):
return wrapper1(wrapper2(arg, func))
@wrapper
def func(x):
return x**2
else
Statements¶Coconut supports the compound statements try
, if
, and match
on the end of an else
statement like any simple statement would be. This is most useful for mixing match
and if
statements together, but also allows for compound try
statements.
Coconut:
if invalid(input_list):
raise Exception()
else: match [head] + tail in input_list:
print(head, tail)
else:
print(input_list)
Python:
from collections.abc import Sequence
if invalid(input_list):
raise Exception()
elif isinstance(input_list, Sequence):
head, tail = inputlist[0], inputlist[1:]
print(head, tail)
else:
print(input_list)
except
Statements¶Python 3 requires that if multiple exceptions are to be caught, they must be placed inside of parentheses, so as to disallow Python 2’s use of a comma instead of as
. Coconut allows commas in except statements to translate to catching multiple exceptions without the need for parentheses, since, as in Python 3, as
is always required to bind the exception to a name.
Coconut:
try:
unsafe_func(arg)
except SyntaxError, ValueError as err:
handle(err)
Python:
try:
unsafe_func(arg)
except (SyntaxError, ValueError) as err:
handle(err)
pass
¶Coconut supports the simple class name(base)
and data name(args)
as aliases for class name(base): pass
and data name(args): pass
.
Coconut:
data Empty
data Leaf(item)
data Node(left, right)
Python:
import collections
class Empty(collections.namedtuple("Empty", "")):
__slots__ = ()
class Leaf(collections.namedtuple("Leaf", "n")):
__slots__ = ()
class Node(collections.namedtuple("Node", "l, r")):
__slots__ = ()
global
And nonlocal
Assignment¶Coconut allows for global
or nonlocal
to precede assignment to a variable or list of variables to make that assignment global
or nonlocal
, respectively.
Coconut:
global state_a, state_b = 10, 100
Python:
global state_a, state_b; state_a, state_b = 10, 100
Coconut supports the ability to pass arbitrary code through the compiler without being touched, for compatibility with other variants of Python, such as Cython or Mython. Anything placed between \(
and the corresponding close parenthesis will be passed through, as well as any line starting with \\
, which will have the additional effect of allowing indentation under it.
Coconut’s map
, zip
, filter
, reversed
, and enumerate
objects are enhanced versions of their Python equivalents that support reversed
, repr
, optimized normal (and iterator) slicing (all but filter
), len
(all but filter
), and have added attributes which subclasses can make use of to get at the original arguments to the object:
map
: _func
, _iters
zip
: _iters
filter
: _func
, _iter
reversed
: _iter
enumerate
: _iter
, _start
Coconut:
map((+), range(5), range(6)) |> len |> print
range(10) |> filter$((x) -> x < 5) |> reversed |> tuple |> print
Python:
Can’t be done without defining a custom map
type. The full definition of map
can be found in the Coconut header.
addpattern
¶Takes one argument that is a pattern-matching function, and returns a decorator that adds the patterns in the existing function to the new function being decorated, where the existing patterns are checked first, then the new. Roughly equivalent to:
def addpattern(base_func):
"""Decorator to add a new case to a pattern-matching function, where the new case is checked last."""
def pattern_adder(func):
def add_pattern_func(*args, **kwargs):
try:
return base_func(*args, **kwargs)
except MatchError:
return func(*args, **kwargs)
return add_pattern_func
return pattern_adder
Coconut:
def factorial(0) = 1
@addpattern(factorial)
def factorial(n) = n * factorial(n - 1)
Python: Can’t be done without a complicated decorator definition and a long series of checks for each pattern-matching. See the compiled code for the Python syntax.
prepattern
¶Takes one argument that is a pattern-matching function, and returns a decorator that adds the patterns in the existing function to the new function being decorated, where the new patterns are checked first, then the existing.
Equivalent to:
def prepattern(base_func):
"""Decorator to add a new case to a pattern-matching function, where the new case is checked first."""
def pattern_prepender(func):
return addpattern(func)(base_func)
return pattern_prepender
Coconut:
def factorial(n) = n * factorial(n - 1)
@prepattern(factorial)
def factorial(0) = 1
Python: Can’t be done without a complicated decorator definition and a long series of checks for each pattern-matching. See the compiled code for the Python syntax.
reduce
¶Coconut re-introduces Python 2’s reduce
built-in, using the functools.reduce
version.
reduce(function, iterable[, initializer])
Apply function of two arguments cumulatively to the items of sequence, from left to right, so as to reduce the sequence to a single value. For example, reduce((x, y) -> x+y, [1, 2, 3, 4, 5])
calculates ((((1+2)+3)+4)+5)
. The left argument, x, is the accumulated value and the right argument, y, is the update value from the sequence. If the optional initializer is present, it is placed before the items of the sequence in the calculation, and serves as a default when the sequence is empty. If initializer is not given and sequence contains only one item, the first item is returned.
Coconut:
product = reduce$(*)
range(1, 10) |> product |> print
Python:
import operator
import functools
product = functools.partial(functools.reduce, operator.mul)
print(product(range(1, 10)))
takewhile
¶Coconut provides itertools.takewhile
as a built-in under the name takewhile
.
takewhile(predicate, iterable)
Make an iterator that returns elements from the iterable as long as the predicate is true. Equivalent to:
def takewhile(predicate, iterable):
# takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4
for x in iterable:
if predicate(x):
yield x
else:
break
Coconut:
negatives = takewhile(numiter, (x) -> x<0)
Python:
import itertools
negatives = itertools.takewhile(numiter, lambda x: x<0)
dropwhile
¶Coconut provides itertools.dropwhile
as a built-in under the name dropwhile
.
dropwhile(predicate, iterable)
Make an iterator that drops elements from the iterable as long as the predicate is true; afterwards, returns every element. Note: the iterator does not produce any output until the predicate first becomes false, so it may have a lengthy start-up time. Equivalent to:
def dropwhile(predicate, iterable):
# dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1
iterable = iter(iterable)
for x in iterable:
if not predicate(x):
yield x
break
for x in iterable:
yield x
Coconut:
positives = dropwhile(numiter, (x) -> x<0)
Python:
import itertools
positives = itertools.dropwhile(numiter, lambda x: x<0)
tee
¶Coconut provides an optimized version of itertools.tee
as a built-in under the name tee
.
tee(iterable, n=2)
Return n independent iterators from a single iterable. Equivalent to:
def tee(iterable, n=2):
it = iter(iterable)
deques = [collections.deque() for i in range(n)]
def gen(mydeque):
while True:
if not mydeque: # when the local deque is empty
newval = next(it) # fetch a new value and
for d in deques: # load it to all the deques
d.append(newval)
yield mydeque.popleft()
return tuple(gen(d) for d in deques)
Once tee()
has made a split, the original iterable should not be used anywhere else; otherwise, the iterable could get advanced without the tee objects being informed.
This itertool may require significant auxiliary storage (depending on how much temporary data needs to be stored). In general, if one iterator uses most or all of the data before another iterator starts, it is faster to use list()
instead of tee()
.
Coconut:
original, temp = tee(original)
sliced = temp$[5:]
Python:
import itertools
original, temp = itertools.tee(original)
sliced = itertools.islice(temp, 5, None)
consume
¶Coconut provides the consume
function to efficiently exhaust an iterator and thus perform any lazy evaluation contained within it. consume
takes one optional argument, keep_last
, that defaults to 0 and specifies how many, if any, items from the end to return as an iterable (None
will keep all elements).
Equivalent to:
def consume(iterable, keep_last=0):
"""Fully exhaust iterable and return the last keep_last elements."""
return collections.deque(iterable, maxlen=keep_last) # fastest way to exhaust an iterator
In the process of lazily applying operations to iterators, eventually a point is reached where evaluation of the iterator is necessary. To do this efficiently, Coconut provides the consume
function, which will fully exhaust the iterator given to it.
Coconut:
range(10) |> map$((x) -> x**2) |> map$(print) |> consume
Python:
collections.deque(map(print, map(lambda x: x**2, range(10))), maxlen=0)
count
¶Coconut provides a modified version of itertools.count
that supports in
, normal slicing, optimized iterator slicing, count
and index
sequence methods, repr
, and _start
and _step
attributes as a built-in under the name count
.
count(start=0, step=1)
Make an iterator that returns evenly spaced values starting with number start. Often used as an argument to map()
to generate consecutive data points. Also, used with zip()
to add sequence numbers. Roughly equivalent to:
def count(start=0, step=1):
# count(10) --> 10 11 12 13 14 ...
# count(2.5, 0.5) -> 2.5 3.0 3.5 ...
n = start
while True:
yield n
n += step
Coconut:
count()$[10**100] |> print
Python: Can’t be done quickly without Coconut’s iterator slicing, which requires many complicated pieces. The necessary definitions in Python can be found in the Coconut header.
datamaker
¶Coconut provides the datamaker
function to allow direct access to the base constructor of data types created with the Coconut data
statement. This is particularly useful when writing alternative constructors for data types by overwriting __new__
.
For data
objects, datamaker
always returns a constructor that always behaves as the underlying data type’s original constructor, exactly as the data type was declared.
For non-data
objects, equivalent to:
def datamaker(data_type):
"""Returns base data constructor of data_type."""
return super(data_type, data_type).__new__$(data_type)
Coconut:
data Tuple(elems):
def __new__(cls, *elems):
return elems |> datamaker(cls)
Python:
import collections
class Tuple(collections.namedtuple("Tuple", "elems")):
__slots__ = ()
def __new__(cls, *elems):
return super(cls, cls).__new__(cls, elems)
fmap
¶In functional programming, fmap(func, obj)
takes a data type obj
and returns a new data type with func
mapped over the contents. Coconut’s fmap
function does the exact same thing in Coconut.
fmap
can also be used on built-ins such as str
, list
, set
, and dict
as a variant of map
that returns back an object of the same type. The behavior of fmap
for a given object can be overridden by defining an __fmap__(self, func)
method that will be called whenever fmap
is invoked on that object.
Coconut:
[1, 2, 3] |> fmap$(x -> x+1) == [2, 3, 4]
data Nothing()
data Just(n)
Just(3) |> fmap$(x -> x*2) == Just(6)
Nothing() |> fmap$(x -> x*2) == Nothing()
Python:
list(map(lambda x: x+1, [1, 2, 3])) == [2, 3, 4]
import collections
class Nothing(collections.namedtuple("Nothing", "")):
__slots__ = ()
class Just(collections.namedtuple("Just", "n")):
__slots__ = ()
Just(*map(lambda x: x*2, Just(3))) == Just(6)
Nothing(*map(lambda x: x*2, Nothing())) == Nothing()
recursive_iterator
¶Coconut provides a recursive_iterator
decorator that provides significant optimizations for any stateless, recursive function that returns an iterator. To use recursive_iterator
on a function, it must meet the following criteria:
return
s an iterator or generates an iterator using yield
,If you are encountering a RuntimeError
due to maximum recursion depth, it is highly recommended that you rewrite your function to meet either the criteria above for recursive_iterator
, or the corresponding criteria for Coconut’s tail call optimization, either of which should prevent such errors.
Furthermore, recursive_iterator
also allows the resolution of a nasty segmentation fault in Python’s iterator logic that has never been fixed. Specifically, instead of writing
seq = get_elem() :: seq
which will crash due to the aforementioned Python issue, write
@recursive_iterator
def seq() = get_elem() :: seq()
which will work just fine.
Coconut:
@recursive_iterator
def fib() = (1, 1) :: map((+), fib(), fib()$[1:])
Python: Can’t be done without a long decorator definition. The full definition of the decorator in Python can be found in the Coconut header.
parallel_map
¶Coconut provides a parallel version of map
under the name parallel_map
. parallel_map
makes use of multiple processes, and is therefore much faster than map
for CPU-bound tasks. Use of parallel_map
requires concurrent.futures
, which exists in the Python 3 standard library, but under Python 2 will require pip install futures
to function.
Because parallel_map
uses multiple processes for its execution, it is necessary that all of its arguments be pickleable. Only objects defined at the module level, and not lambdas, objects defined inside of a function, or objects defined inside of the interpreter, are pickleable. Furthermore, on Windows, it is necessary that all calls to parallel_map
occur inside of an if __name__ == "__main__"
guard.
parallel_map(func, *iterables)
Equivalent to map(func, *iterables)
except func is executed asynchronously and several calls to func may be made concurrently. If a call raises an exception, then that exception will be raised when its value is retrieved from the iterator.
Coconut:
parallel_map(pow$(2), range(100)) |> list |> print
Python:
import functools
import concurrent.futures
with concurrent.futures.ProcessPoolExecutor() as executor:
print(list(executor.map(functools.partial(pow, 2), range(100))))
concurrent_map
¶Coconut provides a concurrent version of map
under the name concurrent_map
. concurrent_map
makes use of multiple threads, and is therefore much faster than map
for IO-bound tasks. Use of concurrent_map
requires concurrent.futures
, which exists in the Python 3 standard library, but under Python 2 will require pip install futures
to function.
concurrent_map(func, *iterables)
Equivalent to map(func, *iterables)
except func is executed asynchronously and several calls to func may be made concurrently. If a call raises an exception, then that exception will be raised when its value is retrieved from the iterator.
Coconut:
concurrent_map(get_data_for_user, get_all_users()) |> list |> print
Python:
import functools
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
print(list(executor.map(get_data_for_user, get_all_users())))
MatchError
¶A MatchError
is raised when a destructuring assignment statement fails, and thus MatchError
is provided as a built-in for catching those errors. MatchError
objects support two attributes, pattern
, which is a string describing the failed pattern, and value
, which is the object that failed to match that pattern.
coconut.__coconut__
¶It is sometimes useful to be able to access Coconut built-ins from pure Python. To accomplish this, Coconut provides coconut.__coconut__
, which behaves exactly like the __coconut__.py
header file included when Coconut is compiled in package mode.
All Coconut built-ins are accessible from coconut.__coconut__
. The recommended way to import them is to use from coconut.__coconut__ import
and import whatever built-ins you’ll be using. For example:
from coconut.__coconut__ import parallel_map
coconut.convenience
¶It is sometimes useful to be able to use the Coconut compiler from code, instead of from the command line. The recommended way to do this is to use from coconut.convenience import
and import whatever convenience functions you’ll be using. Specifications of the different convenience functions are as follows.
parse
¶coconut.convenience.parse(code, [mode])
Likely the most useful of the convenience functions, parse
takes Coconut code as input and outputs the equivalent compiled Python code. The second argument, mode, is used to indicate the context for the parsing.
Each mode has two components: what parser it uses, and what header it prepends. The parser determines what Coconut code is allowed as input, and the header determines how the compiled Python can be used. Possible values of mode are:
"sys"
: (the default)coconut.__coconut__
to access the necessary Coconut objects."exec"
:exec
at the global level, this header will create all the necessary Coconut objects itself instead of importing them."file"
:--standalone
file and should not be passed to exec
."package"
:--package
file and should not be passed to exec
."block"
:exec
if code with a header has already been executed at the global level."single"
:"eval"
:"debug"
:setup
¶coconut.convenience.setup(target, strict, minify, line_numbers, keep_lines, no_tco)
setup
can be used to pass command line flags for use in parse
. The possible values for each flag argument are:
None
(default), or any allowable targetFalse
(default) or True
False
(default) or True
False
(default) or True
False
(default) or True
False
(default) or True
cmd
¶coconut.convenience.cmd(args, [interact])
Executes the given args as if they were fed to coconut
on the command-line, with the exception that unless interact is true or -i
is passed, the interpreter will not be started. Additionally, since parse
and cmd
share the same convenience parsing object, any changes made to the parsing with cmd
will work just as if they were made with setup
.
version
¶coconut.convenience.version([which])
Retrieves a string containing information about the Coconut version. The optional argument which is the type of version information desired. Possible values of which are:
"num"
: the numerical version (the default)"name"
: the version codename"spec"
: the numerical version with the codename attached"tag"
: the version tag used in GitHub and documentation URLs"-v"
: the full string printed by coconut -v
CoconutException
¶If an error is encountered in a convenience function, a CoconutException
instance may be raised. coconut.convenience.CoconutException
is provided to allow catching such errors.
Anyone is welcome to submit an issue or pull request regardless of whether or not they have read this document. The purpose of this document is simply to explain the contribution process and the internals of how Coconut works to make contributing easier.
If you are thinking about contributing to Coconut, please don’t hesitate to ask questions at Coconut’s Gitter! That includes any questions at all about contributing, including understanding the source code, figuring out how to implement a specific change, or just trying to figure out what needs to be done.
Contributing to Coconut is as simple as
develop
branch, andWant to help out, but don’t know what to work on? Head over to Coconut’s open issues and look for ones labeled “contributor friendly.” Contributor friendly issues are those that require less intimate knowledge of Coconut’s inner workings, and are thus possible for new contributors to work on.
DOCS.md
FAQ.md
HELP.md
Makefile
make dev
will automatically install the full Coconut developer environment.setup.py
requirements.py
and constants.py
to install Coconut. Also reads README.rst
to generate the PyPI description.conf.py
coconut
__coconut__.py
__init__.py
%coconut
IPython magic.__main__.py
main
from main.py
.constants.py
convenience.py
cmd
, version
, setup
, and parse
functions as convenience utilities when using Coconut as a module. Documented in DOCS.md
.exceptions.py
highlighter.py
main.py
main
and main_run
, the entry points for the coconut
and coconut-run
commands, respectively.requirements.py
constants.py
into a form setup.py
can use, as well as checks for updates to Coconut’s dependencies.root.py
root.py
creates and executes the part of Coconut’s header that normalizes Python built-ins across versions. Whenever you are writing a new file, you should always add from coconut.root import *
to ensure compatibility with different Python versions. root.py
also sets basic version-related constants.terminal.py
logger
, which is Coconut’s primary method of logging a message from anywhere.__init__.py
command.py
.cli.py
ArgumentParser
object used to parse Coconut command-line arguments.command.py
Command
, whose start
method is the main entry point for the Coconut command-line utility.mypy.py
--mypy
flag.util.py
command.py
, including Prompt
for getting syntax-highlighted input, and Runner
for executing compiled Python.watch.py
--watch
flag.__init__.py
compiler.py
.compiler.py
Compiler
, the class that actually compiles Coconut code. Compiler
inherits from Grammar
in grammar.py
to get all of the basic grammatical definitions, then extends them with all of the handlers that depend on the compiler’s options (e.g. the current --target
). Compiler
also does pre- and post-processing, including replacing strings with markers (pre-processing) and adding the header (post-processing).grammar.py
Grammar
, the class that specifies Coconut’s grammar in PyParsing. Coconut performs one-pass compilation by attaching “handlers” to specific grammar objects to transform them into compiled Python. grammar.py
contains all basic (non-option-dependent) handlers.header.py
getheader
, which generates the header at the top of all compiled Coconut files.matching.py
Matcher
, which handles the compilation of all Coconut pattern-matching, including match
statements, destructuring assignment, and pattern-matching functions.util.py
grammar.py
.__init__.py
icoconut/root.py
.__main__.py
root.py
__coconut__.pyi
__coconut__.py
).tests
__init__.py
main_test.py
.__main__.py
make test
, or a pytest
command to run a specific test, is necessary.main_test.py
TestCase
subclasses that run all of the commands for testing the Coconut files in src
.src
extras.coco
convenience.py
and icoconut.runnable.coco
--arg
was passed when running the file.runner.coco
main
from cocotest/agnostic/main.py
.cocotest
__init__.coco
main.coco
asserts exists.main.coco
specific.coco
--target
.suite.coco
util.coco
.tutorial.coco
TUTORIAL.md
.util.coco
suite.coco
.py2_test.coco
--target 2
.py3_test.coco
--target 3
.py35_test.coco
--target 3.5
.Coconut (coconut-lang.org) is a variant of Python built for simple, elegant, Pythonic functional programming.
Coconut is developed on GitHub and hosted on PyPI. Installing Coconut is as easy as opening a command prompt and entering:
pip install coconut
after which the entire world of Coconut will be at your disposal. To help you get started, check out these links for more information about Coconut:
Note: If the above documentation links are not working, try the mirror .
").html($(this).html());
});
}});
// Update sourcelink to remove outerdiv (fixes appearance in navbar).
var $srcLink = $(".nav #sourcelink");
$srcLink.parent().html($srcLink.html());
});
}(window.$jqTheme || window.jQuery));PK fJ>u coconut-v1.2.3/_static/up.pngPNG
IHDR 7 IDATx@ez $& 8:& :Kpwn}O<:!!{G@Dz?"̧ S{g<ݢ lMQwy|?
0 pq8q` pL-'SBNAwTń|UV IENDB`PK fJS^[W W &