Stack Builders logo
Arrow icon Insights

Enhancing Functional Programming in Python with Coconut

In this blog, I’ll mention some drawbacks of functional programming in Python and how to overcome them with the out-of-the-box features provided by the Coconut programming language. Additionally, I will demonstrate how to use this functional programming language to build a simple app and showcase how this language integrates seamlessly with other Python libraries.

Python is a multi-paradigm language that can follow different paradigms to solve a problem; this includes functional programming, which I enjoyed after learning Haskell and Elm. Python offers a lot of functional features like anonymous functions, high-order functions, decorators, lazy evaluation (through iterators), and functional helpers (through functools) and I can use the language's core feature to have custom operators for common operations (e.g. the piping operator). However, lambdas are too verbose, not all functions are curried, and I will need to create additional helpers or depend on external libraries to write functional code to maintain a single style for my application.

In this blog, I'm going to show how Coconut enhances the experience of writing functional Python and I'll build an application to show its features and how it can work fine in a Python environment.

NOTE: I want to strongly highlight that this blog is based on my personal experience with functional programming in Python. I'll list some inconveniences that I found in Python but (maybe) they're not enough reasons to look for a switch to another language. As always, use your own reasoning and analyze your situation to make a decision.

## Why I don't like Functional Programming in Python

Functional programming is my preferred paradigm for solving problems because I need to think about small functions that are easy to read, understand, test, and compose. Python has many features like lambda functions, decorators, high-order functions, lazy evaluation (e.g. through iterators), and so on that can enrich my functional programming experience. However, there are some small things that I (personally) don't like:

1. Lambda functions need the lambda keyword which is a long word for an anonymous function, especially when we need to use many of them. For example:

queen_song_stars = map(
  lambda song: song.stars,
    filter(lambda song: song.author == 'Queen', 
      filter(lambda song: song.is_favorite, songs)))

In fact, lambda functions can be overused and in many cases, they may not be recommended.

2. Many common functions like compose or pipe are not defined out-of-the-box so I need to define them manually:

def pipe(x, *fns):
   pass

def compose(f, g):
   pass

An option could be to use existing libraries that contain those functions (e.g. functools or PyToolz) but then I need to maintain that dependency (e.g. making sure it's up to date to avoid possible Common Vulnerabilities and Exposures).

3. Not all functions are curried so I need to curry them manually

def filter_by_artist(artist: str) -> Callable[[str], songs]:
    def wrap(songs: Iterator[Song]) -> Iterator[Song]:
        return filter(lambda song: song.author == 'Queen', songs)
    return wrap

I could use functools.partial but then I need to import that every time I need to curry a function.

4. It's necessary to import utilities or other libraries every time I need to use these operators

from utils import compose
from toolz import pipe
import statistics

get_stars_of_artist = compose(filter_by_artist, get_stars)

pipe(
  tenacious_d_songs,
  get_stars_of_artist,
  statistics.mean
)

This is a little bit of a consequence of the previous two points but it doesn't need to be an issue if I use some tooling to get the import automatically (e.g. using a Language Server Protocol and integrating it with my editor) and a code linter that tells me when I don't need a dependency anymore so that it can be removed.

These nuisances become great inconveniences for me when doing functional programming in Python but they may not be a big deal for most developers. Fortunately for me, I found Coconut which has the functionalities that I was looking for.

How did Coconut satisfy my needs?

Coconut is a variant of Python that adds new functional programming features (and others) on top of it. Coconut's built-in helpers solve the inconveniences that I mentioned before and more. In Coconut, I don't need to create helpers, import packages, add additional dependencies, wrap functions, create abstractions, overload operators, etc., in order to have the functional experience that I want. This is because the solutions are available out-of-the-box.

Let's take a look at this example as a sneak peak:

import statistics

tenacious_d_songs = [
  { "name": "Tribute", "stars": 4 },
  { "name": "Master Exploder", "stars": 5 },
  { "name": "Kickapoo", "stars": 4 }
]

result = (
  tenacious_d_songs
  |> map$((x) => x['stars'])
  |> statistics.mean
  |> (x) =>
      if x >= 4
      then "Cool music!"
      else "Regular"
)

result |> print

We can note the following things:

  1. We have a pipe operator (|>) out-of-the-box (in fact, we have many more)

  2. Arrow functions are simple and less verbose: (x) => x['stars']

  3. We can curry a function with $ so no need to import other packages and wrap our functions.

  4. We have a different syntax for inline if/else statements that clarify what happens on both sides of a conditional

Let's be clear about something, though. Coconut is not the holy grail either; it has a lot of things that need to be improved to be used more broadly (e.g. a better developer environment) but it offers nice functional features for Python developers to enjoy functional programming.

Coconut used for an Application

I'll create a simple App that will crawl the website of Luciano Mellera to get a list of shows and a web app that summarizes the information obtained and allows me to know if there will be shows in my city. The project is structured in the following way:

crawler/
  __main__.coco
web/
  templates/index.html
  __main__.coco
requirements.txt

Crawler

The crawler was made of 4 things:

  1. A data type that will hold the information from the Show
@dataclass
class Show:
  date: str
  place: str

  def to_dict(self):
    return { "date": self.date, "place": self.place }
  1. A show parse given a tag will instance a Show

    a. This function presents something very particular and that is the else statement that returns a Show if the tags are found otherwise None.

def parse_show(tag) -> Show | None:
  match (tag.find('h2'), tag.find('h4')):
    case (d `isinstance` bs4.element.Tag, place `isinstance` bs4.element.Tag):
      return Show(place=place.get_text(), date=d.get_text())
    case _:
      return None
  1. A function that parses all the shows filtering the None cases and getting a list out of it.

    a. I'm using the (\>) operator which is very neat because it's a monadic operator (like bind) for union types of the form: X | None

def parse_shows(html_doc: str) -> list[dict]:
  soup = bs4.BeautifulSoup(html_doc, 'html.parser')
  return (
    soup.find_all('section', class_="elementor-section")
    |> map$((x) => parse_show(x) |?> (x) => x.to_dict)
    |> filter$(None)
    |> list
  )
  1. A composition of all of the above

    a. There is something in particular in this case and that is safe_call which converts the result of request.get into an Expected data type that is similar to Either in Haskell. This type represents a computation that could fail or succeed.

if __name__  == "__main__":
  with open("shows.json", "w") as f:
    match safe_call(requests.get, URL):
      case Expected(result, error=None):
        shows_json = (
          result.content
          |> parse_shows
          |> json.dumps
        )
        f.write(shows_json)
        print("Done!")
      case Expected(_, error=error):
        print(f"Sorry, there was an error: {error}")

Web App

The Web App is going to be something very simple as well. It will read the data from the crawler and summarize it to present it in a simple way. In fact, it's a simple Flask view function:

ffrom flask import Flask, render_template

import json

app = Flask(__name__)

@app.route("/", methods=["GET"])
def home():
  with open("shows.json", 'r') as f:
    shows = f.read() |> json.loads
    return render_template(
    "index.html",
        cities=shows |> map$((x) => x['place']) |> set,
        available_shows=shows |> filter$((x) => 'Agotado' not in x['place']),
        show_in_quito=(
            if any('Quito' in x['place'] for x in shows)
            then 'Yes!!!'
            else 'Nope :('
        ),
   )

We're not doing anything extraordinary here. It's a simple view with three parameters that are passed to the template where they're processed to generate the final HTML:

<body>
    <div class="cities">
    <h1>All Cities</h1>
    <ul>
    {% for city in cities %}
          <li>{{ city }}</li>
    {% endfor %}
    </ul>

    <h1>Available</h1>
    <ul>
    {% for x in available_shows %}
          <li>{{ x['place'] }} - {{ x['date'] }}</li>
    {% endfor %}
    </ul>

    <h1>Will he be in Quito?</h1>
    <p>{{ show_in_quito }}</p>
    </div>
  </body>

The main point of this part was to show that it's possible to integrate Coconut with the Python libraries (e.g. Flask, Jinja, requests, beautiful soup that were used in this post) with no hustle. All the code for this blog post is in GitHub. Take a look at it, try it out, play around with it, etc! For running the crawler and web projects you can use coconut-run:

coconut-run crawler
coconut-run web

Conclusion

In this post, I show how Coconut can help overcome some small inconveniences for functional programming in Python with out-of-the-box features. I presented some features like simple array functions, operators for piping data (|>, |?>) and curry functions ($), pattern matching, operators and data types for optional and exceptional data types, and that is just the tip of the iceberg. Finally, I presented two simple and real applications of Coconut to show how it can be used to work with other Python packages without difficulties.

In this post, I presented code snippets to demonstrate Coconut’s functionality. In a future post, I will cover how to test, build and deploy a Coconut application using the coconut compiler.

Published on: Feb. 3, 2025
Last updated: Feb. 03, 2025

Written by:

Cris-Motoche
Cristhian Motoche

Subscribe to our blog

Join our community and get the latest articles, tips, and insights delivered straight to your inbox. Don’t miss it – subscribe now and be part of the conversation!
By subscribing to Stack Builders Insider, you agree to our Privacy Policy.