Skip to content

Instantly share code, notes, and snippets.

@cmhobbs

cmhobbs/hy.md Secret

Created October 12, 2022 20:47
Show Gist options
  • Select an option

  • Save cmhobbs/023c8aab9b91078d6a6c8ffff1e8d233 to your computer and use it in GitHub Desktop.

Select an option

Save cmhobbs/023c8aab9b91078d6a6c8ffff1e8d233 to your computer and use it in GitHub Desktop.
hy.md

Hy, World!

Programming isn't always a serious affair. Sometimes it's nice to have fun and once in a while that fun translates to a useful thing. I often like to tinker with Lisp dialects and more recently I spent some time with Fennel. I don't know Lua as well as I know Python so a quick search for a "Pythonic Lisp" lead me to find Hy.

Over a weekend and during one of our lab days, I played a little bit with Hy. It's a clever little Lisp dialect that rides on top of Python. Hy builds down to Python AST objects (rather than bytecode) and that lets you have access to all of the Python in your Lisp. It's a bit like Clojurescript is to JavaScript or clang is to LLVM.

My first question with Hy is the same as most, I think: "Why?". It's clearly not a "complete" Lisp implementation and it's very niche. Only a few hundred users and not a lot of activity as far as I can tell. The Hy Documentation has its own list of reasons for why it was created. For me it boils down to my love for Lisp syntax, no significant whitespace (sorry, not sorry), homoiconicity, and access to macros (I believe Hy was created before PEP-638).

Here's a quick example of a simple RPN calculator I wrote in Python and a direct port of it in Hy:

import operator

ops = {
    '+': operator.add, 
    '-': operator.sub, 
    '*': operator.mul, 
    '/': operator.truediv
    }

stack = []

while True:
    n = input()
    if n in ops:
        y,x = stack.pop(),stack.pop()  # reversed because RPN...
        z = ops[n](x,y)
        stack.append(z)
        print(f"result: {z}")
        print(f"stack: {stack}")
    else:
        stack.append(int(n))        
        print(f"stack: {stack}")
(import operator)

; two spaces to separate assignment here is kinda tricky
(setv 
  ops {"+" operator.add  "-" operator.sub  "*" operator.mul  "/" operator.truediv}
  stack [])

(while True
  (setv n (input))
  (if (in n ops)
    (do 
      (setv 
        y (int (.pop stack))
        x (int (.pop stack))
        z ((get ops n) x y))
      (.append stack z) ; some python oop mixed into my lisp
      (print "result: " z)
      (print "stack: " stack))
    (do
      (.append stack n)
      (print "stack: " stack))))

It's worth noting that my Hy implementation is not very Lispy and that's one of the great things about the language. It's flexible enough to do things in a Pythonic way if you like or keep it more functional. I'm especially excited about this because functional programming feels like a second class citizen in Python.

The example also clearly shows that you can use Python libraries natively without any trouble. Hy will even integrate pretty seamlessly with pytest and pdb/ipdb. The interoperability goes both ways, too. It is possible to write modules in Hy and import them in your Python source with no trouble at all.

Hy isn't perfect, though. It lacks some features of Lisp like closures or the tight integration between REPL and editor. There are vi(m) and emacs scripts for it but tooling for other editors is sparse (generic Lisp syntax highlighting works sometimes). The community is quite small; consisting of a slow github discussion board, about 8 people in an IRC channel, a mostly dead subreddit, and about 93 questions on StackOverflow at the time of writing.

So once again why use Hy? Well, Lisp is rad and so is Python. If you happen to be hacking in a primarily Python environment and you want some Lisp features, pick up Hy. It is certainly a specialized tool but it's a great deal of fun.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment