Why Hy?
My second programming language was a Lisp. I fell in love with the expressiveness of Lisps.
But most Lisps are obscure. The ecosystem is not suited for beginners.
One of younger Lisps, Clojure, was my first foray into the world of web-development.
I failed spectacularly because I didn’t know how web-develoment worked, and the Clojure ecosystem assumes too much previous knowledge.
Other languages, e.g., Racket, are more approachable, but are still too unpopular to have a big community.
Let’s face it, Lisps are no mainstream programming languages.
Python is beginner-friendly. It has a big ecosystem suited for web development, machine-learning and more.
Enter Hy, a Lisp dialect that transforms into the Python abstract syntax tree.
You can use all the goodness of Python, but with a Lisp.
Every Python package can be used with Hy, and vice versa.
Let’s create a simple “Hello World” web app with Flask, Hy and Docker.
(Flask is a lightweight web application framework for Python.)
Installation
You’ll need Docker.
I also use a Makefile to run the Docker commands, but that’s optional.
I’m on a Linux computer. MacOs should work, too. For Windows, I recommend setting up WSL 2.
Create a new folder called hy-flask
on your computer.
(All the code is available on GitHub.)
Dockerfile
Inside the hy-flask
folder, we’ll create a Dockerfile
with the following content:
# base image
FROM python:3.8.4-buster
# create user
ARG USER_ID=1000
ENV USER_ID $USER_ID
ARG GROUP_ID=1000
ENV GROUP_ID $GROUP_ID
# add non-root user and give permissions to workdir
RUN groupadd --gid $GROUP_ID user && \
adduser user --ingroup user --gecos '' --disabled-password --uid $USER_ID && \
mkdir -p /usr/src/app && \
chown -R user:user /usr/src/app
# virtualenv
ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# set working directory
WORKDIR /usr/src/app
# install system dependencies
RUN apt-get update && apt-get clean
# add and install requirements
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# switch to non-root user
USER user
# disables lag in stdout/stderr output
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
# Path
ENV PATH="/opt/venv/bin:$PATH"
# run
CMD ["python", "-m", "shim"]
We start from a Debian Python image. Then we create a non-root user.
Next, install the Python packages into an isolated virtual environment.
The Docker container will start a Python program called shim.py
. I will come back to that later.
Let’s first create a requirements.txt
file with all dependencies inside our main folder (hy-flask
):
Flask==1.1.2
hy==0.19.0
Our First Flask Application
Here is a minimal “Hello, world!” example with Python:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run()
Now the same with Hy.
Create a folder called app
within your project’s directory.
Make a new file called app.hy
(not .py
).
#!/usr/bin/env hy
(import [flask [Flask]])
(setv app (Flask "__main__"))
(with-decorator (app.route "/")
(defn index []
"Hello, world!"))
We import Flask, then we bind the variable app with setv
.
with-decorator
is used to wrap a function with another, the same as Python.
We also define a function with defn
. The function takes no parameters ([]
) and returns "Hello, world!"
.
The app won’t run yet.
Our Docker container runs a start script called shim.py
. The Python program is a work-around.
Why do we need that?
We want to tell the Flask app to run on host 0.0.0.0
. Per default, the Flask server runs on localhost
and you cannot see it outside of your network.
Docker creates a separate network which we can forward to outside the container. We need to run the Flask application on an externally visible server.
In Python, this would look like this:
# previous code
if __name__ == '__main__':
app.run(host=0.0.0.0)
Let’s create the shim. Make a new Python file called shim.py
inside the app
folder with the following content:
import hy
from app import app
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")
(This code is from Paul Tagliamonte.)
Build And Run Docker
We can now build the Docker container. Run the following command inside the terminal in the main project folder (hy-flask
):
docker build -t hy-flask .
After you’ve build the container, you can run it:
docker run --rm -it -v "${PWD}/app":/usr/src/app -p 5000:5000 hy-flask
This runs an interactive (-it
) Docker container which will be removed (--rm
) after you’ve stopped it.
The -v
flag binds a volume. For development, we mount our local app
folder into the container.
Now we don’t have to rebuild the container even if we change the code.
We also publish the port 5000. Now we can see the app on our computer.
Go to http://localhost:5000
and admire your Hy Flask application!
Optional:
Create a Makefile
with the following content (inside the main folder):
all:
@echo "Usage: build or run"
build:
docker build -t hy-flask .
run:
docker run --rm -it -v "${PWD}/app":/usr/scr/app -p 5000:5000 hy-flask
You can build the container with make build
. Run the container with make run
.
You can see all the code on GitHub.
Done
That’s it! All you need to do to get a Docker container running with Hy and Flask.
The Python shim threw a wrench into the spanner, but other than that, the translation from Python to Hy is straightforward.
Further Reading
- A Lisp Programmer Living in Python-Land: The Hy Programming Language by Mark Watson
- What is a Makefile and how does it work? (Opensource.com)
- A Linux Dev Environment on Windows with WSL 2, Docker Desktop and More by Nick Janetsakis