Coconut Lang
Over the past few years, I have had grown to have a love-hate relationship with my primary language: python. The majority of this frustration with my first love stems from my other favorite language: Elm.
The primary point of contention between my two favorites comes down to developer experience. With elm, development is fun, and I feel like things just work when I expect them to. When reviewing code, I can look at the signatures to figure out how data is flowing and elm does a really good job of managing signal to noise when it comes to code readability. That and the fact that I can proof a solution to a problem makes developing a breeze and a joy.
Contrasting it with python. Now my python development experience isnt all that far off. I make heavy use of mypy
and yapf
to replicate the type safety and elm-format experience. I make heavy use of dataclasses with frozen=True
to make sure that I am using immutable data wherever possible. I have written libraries like data-records
and functional-pipeline
to try and emulate the experience of writing elm in python. And for the most part, I can get close. However a lot of the guarantees I get are weak, and trying to get other programmers on my team to write that way is a lot harder.
I have been trying to pick up Haskell or another pure fp and typed language to go on the backend, but it is hard to beat the Python Ecosystem for building, testing, deploying projects. I recently lamented to a coworker about how I feel the engineering disicipline has strayed away from Computer Science Purity in a lot of cases. My primary example was learning Rust. I know that I can write an API in rust, and have it be brilliantly fast, efficient and type safe. I know that I could deploy that Rust API to a server and have it run 100-1000x faster than the equivalent python code. However, due to trends like serverless, there is no point for that efficiency. It is easier for me to deploy a python API on a lambda, and pay near nothing for it, then it is for me to spend the time to write the best Rust based API and deploy it to a server. The server will cost me thousands of times more in most cases, and it would have been slightly slower development.
So while reflecting on this predicament, I wondered “what if I could run elm in python”. It fits in my long term goals of writing a programming language, this would give me the ability to try the implementation with a well defined language. Whats more, Elm transpiles to javascript (a language famous for weak guarantees), so why not have it transpile to python? Before I got too far down the road, I wanted to check for prior work. I happened across upon this list. After perusing the one that stood out to me was coconut.
Coconut is a transpile to python functional programming language that is a superset of python. It adds a bunch of sytnax and helper functions, but ultimately any valid python is valid coconut. So I thought I would try it out for a few things.
One of the first problems I tried out came from a post in the #python
channel at work
1 | A Primorial is a product of the first n prime numbers (e.g. 2 x 3 x 5 = 30). 2, 3, 5, 7, 11, 13 are prime numbers. If n was 3, you'd multiply 2 x 3 x 5 = 30 or Primorial = 30. |
I had posted an answer along the lines of
1 | from functools import reduce |
Now to start off with, this is already a pretty clean and straightforward piece of
python. It is very declarative, it has a clean separation of concerns with it’s
functions, and overall very readable. I did take a pass using my functional-pipeline
1 | ... |
That feels a lot more elm-like and doesnt require the intermediate variables.
Lets take a look at coconut:
1 | product = reduce $ (*) |
Okay lets break that down, first we can define product
as a partial application
of reduce
. reduce $ (*)
is functionally equivalent to
1 | from functools import reduce, partial |
Except in the coconut example, no imports are needed and we can use the operator itself instead of the named operator like we need to in python.
The next part:
1 | match def is_prime(n is int if n < 2) = False |
could have been defined exactly how I did it in python. The definition was already
a simple oneliner and was readable. However I decided to use the language feature
of guards and create a pattern matched function using match
and addpattern
. This
selects the use case based on the arguments, which makes defining more complex
logic a lot simpler.
Then lastly,
1 | def primorial(n is int) = count() |> filter $ (is_prime) |> .$[:n] |> product |
So because I didnt metion it before, the def <func_name>(<args>)=
syntax makes
for a function with an implicit return. For simple functions this saves
writing the return
keyword.
So here we have the same pipeline as before, but I can compose a filter by partially
applying is_prime
with filter$(is_prime)
. I can define take
by partially
applying slice with .$[:n]
. So here is a lot of nice shortcuts for things we
do all the time in functional languages but are a pain in the ass in python. And did
I mention all of the beautiful |>
pipes!
Another Example from Jornaya’s #python
channel:
1 | Write a program that accepts a space-separated sequence of words as input and prints the words in a space-separated sequence after sorting them alphabetically. |
My original python
1 | def sort_sentence(words: str) -> str: |
And the resultant coconut
1 | def sort_sentence(sentence) = sentence |> .split(' ') |> sorted$(?, key=.lower()) |> " ".join |
Here I get to use the ?
in partial application and the .lower()
partial application,
both of these make the pipeline so much cleaner and easier to read in my opinion.
Overall I enjoy coconut, the code it produces it is a little less than readable, but it works and it executes in an python environment and can be intertwined with anything in python’s extensive ecosystem. I dont know if I would ever use it in production, but it has given me a respite on programming in non-provable systems.