Hacker News new | past | comments | ask | show | jobs | submit login
Hy – A dialect of Lisp that’s embedded in Python (hylang.org)
313 points by colinprince on Dec 3, 2014 | hide | past | favorite | 77 comments

Hy is a self-compiling macro. Macros are just programmable mini-compilers embedded in the host language. This lets you do neat things like take a declarative description of a process and compile it into an executable function:


I gave a talk at Pycon last year that discussed, among other things, the implementation of a constraint solver for games. You can implement an answer-set language on top of it in order to make it easier to use for yourself or collaborators in pure-Python... but writing AST-transforming code using Python's `ast` module is a huge pain. Just sprinkle some parens around and you can treat the Python AST as if it were just another plain-old-datastructure. It becomes magnitudes easier to write your answer-set solver's language interface.

And because Hy is freely importable from Python code you can simply use it to write that front-end compiler for your constraint solver. The rest of your code can be in Python if that works better for you.

But it doesn't stop there... you also get Hy's core library. Which provides some nice higher order functions and macros which compile down to really nice, idiomatic Python code. The kind of code you'd want to write to express that idea.

It's a nice tool to have.

(as a contributor I'm slightly biased).

FYI, this is what's been eating into my Clojure output. The Hy REPL starts up instantly and the interop is so good I find myself writing small projects in Hy instead.

Here's a wiki implemented with it (demonstrates using Bottle, doing URL routing and few other things): http://github.com/rcarmo/sushy ...and here's a little playground with various snippets of code: https://github.com/rcarmo/hy-there

...and a feed fetcher: https://github.com/rcarmo/mqtt-feed-fetcher/blob/master/main...

...and a prototype for a nicer REPL: https://github.com/rcarmo/hyrule

Edited to add: The killer feature here is cross-interop: I can re-use my code in either direction (and have run Hy in PyPy, IronPython, etc. -- sadly, not in Jython yet)

i started migrating a project from clojure to common lisp so that i could move the ui from swing to qt. i should really give hy + pyqt a try as well.

pyqt works pretty well with it. I just gave it a try with QGIS (a open source GIS application) and it uses PyQt


Here's a minimal dialect of Lisp embedded in PHP, in 272 lines:


It could be shorter, but as written, it can use almost all PHP's operators (as dynamically generated functions), interact with PHP classes and functions, and produce closures that PHP code can call.

Actual code sample (albeit a poor one):


Implementation of map could be done like this (tested and it works):

      y(y('map', y('lambda',
          y('func', 'data'),
              y('==', 'nil', 'data'),
                  y('func', y('car', 'data')),
                  y('map', 'func', y('cdr', 'data'))
      /* ... */

That's fascinating. What is performance like?

I've done absolutely no profiling on it, but I suspect it isn't exactly fantastic, particularly since any function call involves a linked-list to PHP array conversion, then array to internal PHP parameter list conversion.

Related: an article comparing Python and Clojure, called "Clojonic: Pythonic Clojure".


What are the particular advantages of a Lisp on the python runtime?

Interop with existing programs could be good, as well as a potential trojan horse to convert a dev shop to Lispier languages if you care about that kind of thing.

Otherwise I'm not sure - what python libraries stand out as particularly useful in this situation? I haven't really interacted with the world of python that much so I'd love to hear.

If you are a lisp-enthusiast, Python comes with strong support for data science and web applications, if nothing else.

If you are a Python enthusiast, lisp-like languages can be used to write DSLs or cut down on boilerplate code in a way which isn't as easy in Python or similar languages.

But yes, in general, a lisp dialect on Python has the problem that Python itself is a great language.

Python has one of the biggest standard libraries there is, there is a lot of useful stuff in there already provided, plus there is a humongous amount of third-party packages available through pip. It also generally has a pretty good C FFI, so there's a lot more support of C-libs in Python.

It's not so much about any specific libraries as it is about the volume of support one gets 'for free,' and as well I think Python syntax and semantics alike really get along better with Lisp than some of the other dialects that rely on FFIs (like Clojure to Java/JavaScript, or the number of C FFIs available for Schemes).

Lisps in general don't tend to include much of a standard library, opting for a 'batteries not included' model, with Racket being perhaps the most prominent (and quite deliberate) exception. So the idea of getting the whole Python ecosystem 'free' is potentially very appealing, especially to someone with a Python background (and there are a lot of those someones around, especially with it increasingly being a more popular teaching language than anything else except JavaScript).

Racket (and precursor Scheme for academic reasons) is the Lisp I've interacted the most with besides Clojure, so I never really considered the perspective of a stdlib being so valuable. Clojure->Java and ClojureScript->Js have always seemed super awkward to me, and having a similar naming style could be a great feature.

Still, I'm reminded of Greenspun's tenth rule when reading about this project. Hopefully it will become a popular and well-supported option in its own right.

I find seeing a multipoint dotted Java name in front of an s-expression deeply unsettling somehow, and I never learned Java anyway, so Clojure was of limited utility to me since so much stuff seemed handed off to Java built-ins.

And I think there is an argument to be made against projects like this, because they're so dependent, but I guess in the case of Hy, that dependence is literally the point. I never felt like it was the case with Clojure, because the host language was so very different from the experimental FP playground Clojurists seem to want.

This comes with any Python, out of the box: https://docs.python.org/2/py-modindex.html

What stands out will be different for different people. I use the ftp and telnet libs a lot, I've used the imap and email libs.

Here's a repackaging of a large number of 3rd party libs, packaged as an alternative python distribution, which I use at work: http://docs.continuum.io/anaconda/pkg-docs.html

I've explored twisted (networking), numpy and scipy (math and scientific), and sympy (symbolic math).

IPython's shell and notebook are great work environment enhancers. http://ipython.org/

Here's Python's larger set of 3rd parties: https://pypi.python.org/pypi?%3Aaction=browse

Can a lisper explain how things like the list comprehension might be implemented? Is it a special control structure that hy knows about or is it something you could implement yourself if it wasn't part of hy?

      (, x y)
      (x (range 8)
       y "ABCDEFGH"))
The fact that data is bound to the variables x and y within the list comprehension - is that a magical thing?

I'm probably not explaining it very well but I thought (as a non-lisper) that you can do things like implement new types of control flow directly in lisp. Could you do that here, or has list-comp been coded directly into the language?

Edit: just to add - I think this is a cool project. Really enjoying reading through the docs.

For me, one of the simplest explanations of the difference between macros and functions is the short-circuiting and.

In many languages, and does not evaluate all of its arguments, only as many as it needs to until it finds #f. This sounds like an easy thing to implement, but in most languages it actually isn't, because order of nested operations means that without being asked, it will evaluate it's arguments before our and.

So we can write this broken-and easily enough:

  (define (broken-and . rest)
      [(null? rest) #t]
      [(car rest) (apply broken-and (cdr rest))]
      [else #f]))
But give it the following, and you get an error:

  > (broken-and (zero? 0) (string? "dave") (null? '(is fat)) (/ 5 0))

  . . /: division by zero
And that will work so long as all your args are valid. But what will work better is to write a macro. Macros aren't evaluated, they merely splice in their syntax, but we can write them a lot like code, using all our control structures and so forth, and in Scheme/Racket, also some cool pattern matching stuff):

  (define-syntax my-and.v2
    (syntax-rules ()
      [(_) #t]
      [(_ a) a]
      [(_ a b ...) (if a
                       (my-and.v2 b ...)
Now we can try our input from before, and get what we want, because the macro expands recursively and won't ever get to our divide by zero, at least unless all preceding args are true:

  > (my-and.v2 (zero? 0) (string? "dave") (null? '(is fat)) (/ 5 0))
This property of only expanding, not evaluating, is why macros can write all kinds of clever control structures, because you're mangling syntax, not evaluating code.

Fundamentally it's macros and homoiconicity of code and data that give lisp it's expressive power. Combined they grant you the power to inject new syntax / domain specific languages (DSLs) directly into your code. Any new syntax you create can look like normal lisp or can have different syntax evaluation rules like infix math notation [1]. A number of years ago I did a short write-up on stack overflow giving a basic explanation of how macros work in common lisp which may be worth a read [2].

Macros in general work by being a syntax tree -> syntax tree transformation. The neat trick is it's not a static pattern like C - you can leverage functions, conditions, etc to build that syntax tree.

As for comprehensions themselves, if you want to see the power of lisp look at the loop macro. It's a single macro yet gives you the power of normal control loops, list comprehensions, for-each all rolled into one package. [3]

[1] http://data-sorcery.org/2010/05/14/infix-math/

[2] http://www.gigamonkeys.com/book/loop-for-black-belts.html

[3] http://stackoverflow.com/questions/267862/what-makes-lisp-ma...

Python natively supports this sort of list comprehension (https://docs.python.org/2/tutorial/datastructures.html#list-...), and the Lisp source is being translated into a Python AST, so that snippet is probably being directly mapped into whatever AST form is necessary to invoke the list comprehension.

Yes that's exactly what's happening. Hy has two levels of things mostly: a "compiler" that does the low-level mapping of base python constructs (and whatever can be mapped to AST)... here's where list-comp is implemented: https://github.com/hylang/hy/blob/master/hy/compiler.py#L137...

Anything above that is written as Hy macros, in Hy itself: https://github.com/hylang/hy/blob/master/hy/core/macros.hy

There's a lot of other core code that's written in Hy itself too. https://github.com/hylang/hy/tree/master/hy/core

Yes thats correct. https://github.com/hylang/hy/blob/dcf29d3d2a147fe66a19971214... Source for those interested. Note that Hy replaces all - in names for _, thus we can have prettier names :)

Oh, and earmuffs become EARMUFFS, which is also nice.

You can create a list comprehension using a macro. In common lisp, lisp comprehension is implemented by the loop macro. http://www.gigamonkeys.com/book/loop-for-black-belts.html

It is a little more involved, but basically you define a syntax for list comprehension and then parse it using the macro to generate the final result.

So you sort of write stuff that defines your language as a 'macro' (and it looks like the rest of the lisp?) and it executes on your code first to transform it?

Yes, thats where the power of lisp is. Say we want to implement do/while. In Python you would have to go into the compiler. In a Lisp, like Hy. We can just write it as a macro.

We implement yield-from on Python2 using a macro. http://dustycloud.org/blog/how-hy-backported-yield-from-to-p...

You can create macros that look like functions, but the macro actually has access to the the "program code", so you can do arbitrary transformations.

The implementation is easier if you assume that you have exactly two generators. Here is an example in Racket. It uses define-syntax-rule that is the method to define simple macros (there are more advanced options).

(The program also define string->list/string that splits a list in a list of one-character string, because the result of string->list is a list of chars. You can ignore it safety.)

  #lang racket

  (define (string->list/string x)
    (map (lambda (c) (list->string (list c)))
         (string->list x)))

  (define-syntax-rule (list-comp2 (proc v ...)
                        [u1 gen1]
                        [u2 gen2])
    (map (lambda (u1 u2) (proc v ...))
  (list-comp2 (list x y)
    [x (range 8)]
    [y (string->list/string "ABCDEFGH")])

  ;==> '((0 "A") (1 "B") (2 "C") (3 "D") (4 "E") (5 "F") (6 "G") (7 "H"))

> but the macro actually has access to the the "program code", so you can do arbitrary transformations.

I like to compare the way lisp works to Javascript and the DOM. Just like a webpage (or its JS code) has access to its internal structure and can do arbitrary transformations, so does lisp except instead of the DOM you have the abstract syntax tree.

There's an excellent paper on how to build list comprehensions (in Common Lisp) on top of simpler iteration constructs:


I have a (very loose) implementation of this approach at:


Frankly, though, once you've learned the trick, list comprehensions lose a lot of their appeal.

"Real" List Comprehensions in 24 Lines of Lisp[0]

[0]: http://lisp-univ-etc.blogspot.com/2013/01/real-list-comprehe...

Macros (the general Lisp concept) are really interesting. I'm still trying to grok them myself, so if I made any mistakes please downvote and correct away.

A normal Lisp function call might look like this:

(myfunc arg1 arg2 arg3)

where arg1, etc can be literals, variables, expressions. Like in most programming languages, each of the arguments is evaluated and then the function is called with the evaluated values of those arguments. When it's done, a value is returned.

A macro is a lot like a function call:

(mymacro arg1 arg2 arg3)

However, it works differently. First of all, the arguments are passed straight into the macro - if arg1 is something like (+ 1 2), the macro will see (+ 1 2), not 3 (as the function would).

Secondly, the macro is intended to return a list, which can be a function call or another macro call. (The macro could also return a final value.) The list can then be evaluated to either expand another macro, or to call a function to produce a final value. So you can think of a macro as a special sort of function that, rather than taking in values to produce another value, takes in literal code to produce transformed code (which then can be evaluated to produce a final value).

Now, one of the uses of macros that people cite is, for example, implementing your own control flow structures. Why are the two related? Lisps have something which are known as special forms, which look like function calls but don't have their arguments evaluated before being passed in (like how macros work). The reason that matters can be described by a simple example: let's say you wanted to implement a very simple version of 'and' as a function (Clojure syntax):

(defn my-and [left-expr right-expr] (if (not left-expr) false (if (not right-expr) false) true))

However, the problem is that left-expr and right-expr will both be evaluated before my-and can even begin executing, along with the attendant side effects, etc. The problem is that functions can't choose whether or not their arguments should or shouldn't be evaluated - they just get the values of their arguments after they've been evaluated[1]. It's not really possible to implement the traditional short-circuiting 'and' using functions, or many control flow structures where you do not want to evaluate every possible branch unconditionally.

However, macros receive the literal code comprising their arguments, and they can choose to evaluate one, none, some, or all of their arguments depending on their internal logic. This makes them better suited for implementing behavior that resembles those of the Lisp special forms.

[1] This is the difference between call-by-name and call-by-value semantics. Most languages are call-by-value, as described above, but (e.g.) Scala has '=>' and Swift has '@autoclosure' to allow for some sort of call-by-name support.

Rebol has an interesting take on this, in that what Lisp calls "macros" and "functions" are unified in Rebol (as "functions") -- you can specify for each argument whether it is taken as evaluated or unevaluated.

Also related : f-expr, a historical attempt at unifying them http://en.wikipedia.org/wiki/Fexpr .

Abandoned because the near impossibility of compilation when using them ( http://stackoverflow.com/questions/18324743/why-were-fexprs-... ).

And more recently http://web.cs.wpi.edu/~jshutt/kernel.html .

Argument's invalid as Rebol and Red are both members of the lisp family. It is like comparing the similarities of a circle with a ellipse. https://news.ycombinator.com/item?id=8613377

I'm not sure how the argument can be invalid, just because Red and Rebol is a lisp? Sounds like both them and Dylan might be a good inspiration for a "python lisp" (not that that is what hy is doing atm, as far as I can tell). Contrasting a circle with an ellipse can be useful, sometimes ;-)

> Argument's invalid as Rebol and Red are both members of the lisp family.

I was pointing out an approach taken by Rebol that's different from most languages that support macros that might be interesting to people, not making an argument.

For non-lispers (like myself) look at Rust and Sweet.js, both from Mozilla. I've been experimenting with Sweet.js and it's very cool and powerful, and I still have a lot to learn about macros and how/where they can be used.

How to implement list comprehensions in Lisp is an open-ended topic.

For instance, I did it once in a "researchy" way by implementing monads. With the help of CLOS, I was able to define various monads such as the list monad, identity monad, or state transformer monad (the map, join and unit being methods dispatched on the monad type).

Then a generic monadic comprehension macro provides different functionality based on which monad is selected. The list monad gives rise to a list comprehension. The identity monad gives rise to just sequential variable binding. And the state transformer monad ... to something like a pipeline of succcessive state transformations, I think.


If you just want a list comprehension, it's not very difficult. Basically it has to expand to code which (for example) expresses a nested loop that steps every variable over its respective list. The expression being collected by the comprehension is stuck into the middle of this loop, inside a piece of code which collects its value into a list, which is stored in a hidden local variable. (Perhaps two local variables are used, to keep track of the head and tail of the list for the sake of efficiency.)

ANSI Lisp has functional applicators that process multiple lists. For instance if you want to add together values from two lists, you can do this:

  (mapcar '+ '(1 2 3) '(10 20 30)) 

   -> (11 22 33)
If you wanted to form a cross product of the two lists instead, the obvious thing would be to write a mapcar-like function:

  (mapcar-cross '+ '(1 2 3) '(10 20 30))

   -> '(11 21 31 12 22 32 13 23 33)
This function could be used as the target syntax for a comprehension macro so that, say:

  (list-comprehend (+ b a) (a '(1 2 3)) (b '(10 20 30)))

   -> '(11 21 31 12 22 32 13 23 33)
is macro-expanded into

  (mapcar-cross (lambda (a b) (+ b a)) '(1 2 3) '(10 20 30))
Translating (transliterating, really) the list-comprehend syntax into mapcar-cross is not very difficult: it's just some very straightforward nested list manipulation. The macro has to collect the list of variables from the trailing arguments after the expression, to form the (a b) argument list of the lambda. It has to remove the variables from those trailing arguments to produce the list expressions like '(1 2 3) and '(10 20 30) that will be applied to mapcar-cross. Then it has to assemble the mapcar-cross syntax.

Of course, you also have to write mapcar-cross, which is your "run-time support function" for your list-comprehend syntax (usable directly without that syntax, too).

It's beautiful. There's a strange feeling in my heart.

- Someone who built a simplistic lisp->lua compiler a couple of years ago at https://github.com/meric/l2l

It's a kind of rite of passage, I think, to implement a Lisp. I wrote half (the easy half) of ISLISP that ran on the Python VM back in 2005 :)

Serendipity :)

For a moment there I was a bit sad because I thought it had been edited away, but the rampant puns in the charming Quickstart are still around. http://docs.hylang.org/en/latest/quickstart.html


I love Hy and I've started using it several times but it still somehow has a few quirks that I bump into when starting to write serious code. Now it tries to reflect how Python works which isn't always that lispy: basically I'm waiting for more Clojure to creep in :)

Well, that most likely won't change. Hy's goal is to be bidirectional, so there are a few things here and there that doesn't feel as lispy as it should. Like having data returned when you use assoc as an example. The scoping is an issue aswell, like let-over-lambdas doesn't work, and iteration over maps isn't as fluid as it is in Clojure.

They should implement Python's list comprehensions in full. Right now Hy only allows one generator clause and one if clause, something like

    (list-comp (, x y) (x (range 5)) (> x 2) (y (range 2)))
gives an error about list_comp only taking 3 or 4 arguments.

After a quick IRC discussion; you wrote it wrong :D! Using hy --spy so you can see the equivalent python code:

    => (list-comp (, x y) [x (range 5) y (range 5)] (> 2 x))   
    from hy.core.language import range   
    [(x, y) for x in range(5) for y in range(5) if (2 > x)]    
    [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1) .....]

Can submit a PR and fix the docs so this is better displayed!

Doesn't what you wrote work in a different, slower, way?

I had in mind something analogous to

    r = []
    for x in range(5):
      if 2>x:
        for y in range(5):
I think what you wrote probably does:

    r = []
    for x in range(5):
      for y in range(5):
        if 2>x:
The first loop completely skips looping over y if the condition on x fails, the second loop repeatedly tests x.

Hurm yes indeed. Interesting problem. I'll copypasta this and fix up an issue! Nice catch!


I thought this is not possible in python. Can someone explain ? Python doesn't have tail call optimization and hy produces python AST. Am I missing something ?

I tried reading through the source, but I am still a lisp noob. https://github.com/hylang/hy/blob/master/hy/contrib/loop.hy

The entry is misleading. We don't really do TCO. It's just a trampoline calling the loop/recur form until there is nothing left to produce.

Same solution clojure has, as the JVM dosn't allow for TCO.

It feels strangely.. nice.

I was always turned off by Python syntax, but liked the explicitness of Python code. I always liked LISP “syntax” but never learned any LISPs, and Clojure is really dense/implicit IMO. So Hy has a nice and unexpected balance of explicitness/familiarity and aesthetics.

But I wonder why someone would prefer this, aside from aesthetics. Macros?

Can you give an example of where you find Clojure to be implicit?

I don't find the Clojure that I write to be implicit.

Clojure(Script) reads Rubyish to me in the sense that I can't read the code until I understand every primitive. Contrast this with Python which almost reads like a natural language (which I don't say is better, but more explicit IMO).

    (defn widget-c [data owner]
        (init-state [_]
          {:message nil})
        (did-mount [_]
          (let [events (sub (:notif-chan (om/get-shared owner)) :hello (chan))]
              (loop [e (<! events)]
                (om/set-state! owner :message (:data e))
                (recur (<! events)))))))
        (render-state [_ {:keys [message]}]
          (if message
            (dom/p nil message)
            (dom/p nil "Waiting ... waiting ... waiting ..."))))

There is nothing implicit about that. You are instantiating an anonymous class (that's the reify) implementing 3 interfaces (presumably you looked these up since they're user libraries) that you are defining inline.

Compare this to something like a Django Rest Framework:

    class SnippetList(APIView):
        List all snippets, or create a new snippet.
        def get(self, request, format=None):
            snippets = Snippet.objects.all()
            serializer = SnippetSerializer(snippets, many=True)
            return Response(serializer.data)

        def post(self, request, format=None):
            serializer = SnippetSerializer(data=request.data)
            if serializer.is_valid():
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
reify doesn't require any special knowledge that class doesn't. The Python code here is subclassing a single abstract class (APIView) instead of 3, but it has the same "problem" in that you have to know what methods to override. Again this is user code so you probably looked up what APIView needs to work just like you looked up with InitState, IDidMount, and IRenderState.

reify vs class in Clojure vs Python is a matter of idioms. You can definitely create an actual class instead of reifying in clojure, it just isn't necessary. Likewise, you could create an anonymous type in python via type(), but you'd probably get fired and/or shot in most circles for doing so.

I mostly wasn't talking about the high-level structure. Compare these methods:

        serializer = SnippetSerializer(data=request.data)
            if serializer.is_valid():
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
with this:

        (let [events (sub (:notif-chan (om/get-shared owner)) :hello (chan))]
              (loop [e (<! events)]
                (om/set-state! owner :message (:data e))
                (recur (<! events)))))))
In order to have a remote idea what's going on, I need to know about channels, what `events`, `sub`, `go`, `loop`, `recur`, `<!` are. In Python, it's easy to tell syntax and primitives from library classes and methods, but here I'm not sure which is which. That's what I mean by Clojure being dense. Of course it's stupid to assume you read the source without understanding the language first, but “Python after you know some OOP“ is still easier to read for absolute language beginner than “Clojure if you know some FP”. Again, I don't imply that Python is somehow better because of that.

What about Clojure seems dense or implicit to you? Clojure and Python have a similar feel to me. Both are well-designed in the sense they strive for a small set of primitives useful in solving a broad range of problems.

See my reply to your sibling. I appreciate it may be very well designed; just saying there's a barrier for a novice to read it (which is fine, but different from Python). Even if you come from a different background (JS, C#), it's trivial to understand Python code because it reads like English. Both Clojure and Ruby are different because they're more dense (but as a result, probably more expressive). And I'm saying this as someone who doesn't really enjoy Python because it's way too sparse. Oh well, whatever :-). I should go learn some Clojure.

Excellent - will have a play with this tonight. Docs look pretty good too, only thing that stands out right now which I want to dig into a bit more is one of the special forms, specifically the dot one - looks like something that would be tricky to see in an eyeball scan of code, but probably worrying prematurely.

Very cool! I switched from python To clojure sometime back, but Hy looks like something I have To give a try.

Flask apps look so great with Hy :

    (import [flask [Flask]])
    (def app (Flask __name__))
    (with-decorator (app.route "/")
      (defn index []
        "Hello World !"))
I'm really looking forward using it in some side-projects.

It dosn't stop there. Why use the decorators directly when you can get Hy on Meth? https://github.com/hylang/hy/blob/master/hy/contrib/meth.hy

Hmmm. Cute. I can probably drop those straight into my Bottle stuff.

I discovered Hy two weeks ago through this previous discussion: How Hy backported “yield from” to Python 2 https://news.ycombinator.com/item?id=8641126

I am just loving it! I code my data analysis stuff in Python and I've accumulated a significant amount of code written in it. Is there any Py->Hy translator out there?

You don't need to translate it -- you can import modules both ways just fine -- THAT's the killer feature.

But wy?

Being an Emacs head, I like sexp based syntax for edition and navigation. Now I can edit hy like any lisp and push it to python hosting servers.

my 2 cents.

Helluw, one of the core devs here. This was originally an idea just to display whats possible with the Python AST. It's pretty neat. The result is an kinda fancy lisp variant with bidirectional interop, macros and all the fancy lisp stuff.

Is this something that can be integrated easily with existing Python code? I'd be interested in implementing a small module in Hy, rather than Python, and then include/use/consume it from Python.

All the uses I saw listed on the blog were as a replacement for Python, it seemed like, so this might be a direction of integration that wasn't intended.

edit: Perhaps I should read more and ask less. It looks like this is what `import_file_to_module` does, right? :-)

Hy core here!

It works both ways, and bi-directional interop is intended! Just `import hy` then you can import any `.hy` on `sys.path` :D

(yes, this means you can make Hy code pip installable, just like Hy itself!)

Wow. That sounds amazing!

All you gotta do is import hy before the given hy module.

Hy uses a metaimporter to parse the Hy code and hand the Python compiler AST. So you can import any hy file like normal Python and use the code. Its that simple.

Is there some way to incorporate Hy snippets into an existing Python script? The plan is that this would allow me to incrementally translate my Python scripts into Hy (I'd start with the easy bits, obviously).

Does it work with cython?

It will import cython stuff, and vice-versa.


The combination of Hy and python-sh looks really interesting. I am tempted to try using this as a shell, similar to scsh.

By python-sh do you mean http://amoffat.github.io/sh/ ?

Yes I do.

So I tried this out briefly and it works, but without additional work it isn't very convenient. Obviously tab-completion of paths/programs/arguments doesn't work since Hy's REPL doesn't do those things (by default anyway). The way python-sh's imports work is also a bit inconvenient; either you need to explicitly import the command you want to run, or you need to prepend it with 'sh.' (`sh.git` runs git for example).

A custom REPL might be a good way to resolve these issues, I'm going to have to put more work into understanding how both of these things work first though. Nevertheless, I think the idea has promise.

Applications are open for YC Winter 2022

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact