Looking back on these examples ... I feel Python is ugly.
Elm makes so much more sense to me: the syntax, the structure, it's functional style, typed data, and so on. I'm stripping this back to the absolute essentials of Python, which is to say as little as possible to get the job done.
- Build a simple API
- SQLite and Json
- Minimal backend code
- Minimal server setup
That's about it. I need something that interfaces with a simple database, keeping things light. It's for prototypes, and will probably be replaced by another programming language at some stage. Roc looks promising, but I'm not a heavy coding guy, so might have a team by then![^]
Some books have:
- Minor or major errors in the code1
- Outdated dependencies (how many does yours have?)
- Academic language (or verbose terminology)
- Not enough visuals (or poorly labelled ones)
Make sure you're explaining just enough and no more. Aim for simple language and minimal terminology, and for beginners to intermediates, stick to easier to learn languages that instil good habits.
It doesn't feel as intuitive or consistent as Elm. Python's main benefit is it's shorter learning curve.
Perhaps you get shorter code at scale, but is it more readable? I'm not so sure.
- Length of a list?
len([1, 2, 3])
List.length [1, 2, 3]
- Access an element?
[1, 2, 3][0]
List.elemIndex [1, 2, 3]
- Reverse that?
[1, 2, 3][-1]
List.elemIndex (List.reverse [1, 2, 3])
It's also not at all type safe by default (and type annotations are awkward). Even the naming conventions feel messy (capitals and lowercase)! Here are some examples and comparisons for Python versus Elm Lang.
Declaration order matters in Python! You might also want to strongly type your code. It's also important to understand "why functional programming over object-oriented?"?
from typing import List
# Requires `return` to print anything!
def ugly_types(num: List[int]) -> dict:
{ "numbers": num }
> ugly_types([1, 2, 3])
# returns nothing
type alias Numbers =
{ numbers : List Int }
-- Returns data by default ...
niceTypes : List Int -> Numbers
niceTypes =
Numbers
> niceTypes [1, 2, 3]
These can be avoided wherever necessary
Class()
es andClass.method()
s (rather than plain functions).- Stateful applications (rather than stateless and functional)
- A
list
can be mutable, and changed anywhere in the program. That's bad! **Kwargs
and other Python magic.- The concept of
self
(a pretty dumb idea in my opinion) - Declaration order can be important (Pydantic nested class should come before it's use).
Elm has FAR superior types and error messaging, which really save you when refactoring. Python by default, doesn't. Python's error messaging makes you crazy!
For example, with Python:
- Typing is ugly and off by default.
- Concepts like
None
andOptional
require a different mindset toMaybe
. - A good REPL and helpful error messages are a god-send for refactoring.
- Python magic like
@decorators
andextra='keyword arguments
don't exist in Haskell.
Take this piece of code, for example:
simple_loop(l: list, id: int) -> bool:
for item in l:
if todo["id"] == id:
return True
else:
return "False"
> simple_loop([], 1)
> simple_loop([{"id": "string"}, 1])
'False'
There's a ton of problems with this:
None
is implicitly returned, without an error- Different return types are allowed (this will break things)
{"id": "string"}
should fail, but doesn't.- Dictionary could literally be
Any
type, with any combination of types. - In order to enforce typing, more setup is involved.
All of these problems are fixed by default!
With Elm no need for any complicated setup, just:
- Install Elm (simply)
elm repl
to open the interactive repl- All you gotta do is code!
Here's what our code looks like now:
simpleFun l i =
List.map (\list -> list.id == i) l
> simpleFun [] 1
[] : List Bool
An example Elm error message looks like this; it infers the types, spots that
1 == "string"
is impossible, and suggests a fix:
simpleFun [{id = "string"}] 1
-- TYPE MISMATCH ---------------------------------------------------------- REPL
The 2nd argument to `simpleFun` is not what I expect:
6| simpleFun [{id = "string"}] 1
^
This argument is a number of type:
number
But `simpleFun` needs the 2nd argument to be:
String
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
Hint: Try using String.fromInt to convert it to a string?
Large Elm applications can become difficult to understand.
- At scale does Python become more readable?
- Perhaps Python is handy for certain applications?
- Perhaps a functional style is possible?
- Perhaps you just need to memorise syntax differences?
- Imperial College London course (example chapter)
- UV in production?
- UV commands (a quick overview)
- Thonny — a beginner IDE2
- VS Code setup tutorial
Command | Does this |
---|---|
activate |
source .venv/bin/activate alias in .zshrc file |
pip freeze |
A list of currently installed packages |
pip install -r /path/to/requirements.txt |
Install requirements3 |
uv init [folder-name] |
Start uv project |
uv python install [version] |
Install a Python version (or latest) |
uv python list |
List all Python versions installed |
uv python pin |
Create a .python-version file |
uv venv --python python3.11 [my_env] |
Create/Download virtual environment |
source my_env/bin/activate |
Activate virtual environment |
deactivate |
Deactivate (exit) venv |
uv add [package] |
Download and install a package |
uv tree |
List all dependencies (as a tree) |
uv run [command] |
Run the server, run a file, etc |
uv sync |
Sets up a project's "stuff"4 |
uv run uvicorn src.main:app --reload |
Run command for subfolder file5 |
Footnotes
-
This is likely to happen when you're making regular changes to the book. But you've really got to have a good editor (or using Ai) to triple check your changes for continuity errors. For the beginner, it's highly likely they'll get stuck, and there's nothing in the book to keep them right other than context and the student's initiative. ↩
-
You can't run this app while there's a virtual environment running in the terminal. You can set the virtual environment by going to
Tools -> Options ... -> Interpreter -> Python executable
and selecting the path or symlink in yourvenv-folder-name
. ↩ -
If you're using stock Python commands, you'll probably need to preface with
python3 -m
, such aspython3 -m pip install [package]
. If you're usinguv
you don't need to worry about this (just useuv run
etc). You also won't need to worry aboutPATH
or any of that shit (I thinkuv
does that for you). ↩ -
From scratch. Environment, dependencies, and so on. I think this needs a
pyproject.toml
file (and maybe a.python-version
file). Lookup the docs for more info. ↩ -
It seems that the
uv
command must be run from the parent directory that the virtual server lives in. You also need to use themodule-folder.dot_format.py
to call a subfolder's file. ↩