73 comments

[ 2.7 ms ] story [ 138 ms ] thread
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 :)
But wy?
(comment deleted)
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.
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.

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

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?

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.
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?

    (list-comp
      (, 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.

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

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?
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.

http://www.kylheku.com/cgit/lisp-snippets/tree/monads.lisp

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).

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 ...))
         gen1
         gen2))
  
  (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.

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...

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)
    (cond
      [(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 ...)
                       #f)]))
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))
  #f
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.
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.
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.
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.

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.
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.

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

https://github.com/igorw/yolo/blob/master/src/yolisp.php

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):

https://github.com/igorw/yolo/blob/master/src/yolo.php#L19-3...

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

  y('let',
      y(y('map', y('lambda',
          y('func', 'data'),
          y('if', 
              y('==', 'nil', 'data'),
              'nil',
              y('cons',
                  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.
(comment deleted)
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):
          r.append((x,y))
I think what you wrote probably does:

    r = []
    for x in range(5):
      for y in range(5):
        if 2>x:
          r.append((x,y))
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!
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)

Wow. That sounds amazing!
Very cool! I switched from python To clojure sometime back, but Hy looks like something I have To give a try.
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]
      (reify
        om/InitState
        (init-state [_]
          {:message nil})
        om/IDidMount
        (did-mount [_]
          (let [events (sub (:notif-chan (om/get-shared owner)) :hello (chan))]
            (go
              (loop [e (<! events)]
                (om/set-state! owner :message (:data e))
                (recur (<! events)))))))
        om/IRenderState
        (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():
                serializer.save()
                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():
                serializer.save()
                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))]
            (go
              (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.
Does it work with cython?
It will import cython stuff, and vice-versa.
Flask apps look so great with Hy :

    (import [flask [Flask]])
    (def app (Flask __name__))
    (with-decorator (app.route "/")
      (defn index []
        "Hello World !"))
    (app.run)
I'm really looking forward using it in some side-projects.
https://github.com/hylang/hy/blob/master/docs/contrib/loop.r...

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.

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.