I wish there was a IDLE for Hy (HYDLE?), so one could have a minimum effort environment setup to get started. What editors currently support Hylang syntax highlighting?
Went to install the package manually to try Hy out. Figured it was a niche/small enough language that it wouldn't have its own Doom module. I was wrong.
Whenever people bring up Lisp syntax, I like to remind everyone about io language. Its homoiconicity is inspired by Lisp, and almost all good things about Lisp syntax are applied in io, but at the same time it avoids this awkwardness that turns away most of the people (and turns the remaining minority into zealots :-)
So if you design a language and want to use Lisp-like syntax, I recommend to also check out io before steaming ahead.
I played with io some time ago as it was one of the languages covered in Bruce Tate's "Seven Languages in Seven Weeks" which give an introduction to seven programming languages (Ruby, Io, Prolog, Scala, Erlang, Clojure, and Haskell) each with a different philosophy. Io seemed an interesting proof of concept, but are you actually using it for a real project or just to explore?
Yeah, Lisps don't have a monopoly in homoiconicity. In addition to io, the Rebol family is also worth checking out.
Though I have to admit I can not imagine how a person can be so mentally inflexible as to be not able to tolerate Lisp-style syntax. I get that Common Lisp might look a bit old-school at first glance but something like Clojure is basically designed to look aesthetically pleasing.
A language is homoiconic if code in that language is written using the syntax of data literals of that language.
In the case of most Lisps, code is written using the syntax of list and symbol literals. (There are other literals too, of course.)
(I (usually) like apples)
is a nested list literal with four symbols. You can think of symbols as "interned strings"; they're written without quotes for convenience. It's not unlike
["I", ["usually"], "like", "apples"]
in Python, except I had to use actual strings instead of symbols because Python doesn't ship with a standard symbol type.
This is also a nested literal list:
(loop for i below n do (print i))
but this happens to be a list literal that can be "run" (or in Lisp parlance, evaluated) as code.
I've been using hylang lately to build libraries for my python projects. It's a lot of fun, and close enough to Clojure that there's very little learning curve.
I encourage people to try out Common Lisp because, unlike with Hy, you will get: speed, ability to build binaries, truly interactive image-based development (yes, more interactive than ipython), more static type checks, more language features (no closures in Hy last time I checked), language stability… To reach to Python libs, you have https://github.com/bendudson/py4cl My comparison of Python and CL: https://lisp-journey.gitlab.io/pythonvslisp/
Hy is cool because it allows people to explore some lisp concepts in context of libraries they may already be familiar with. And Python has an enviable set of batteries. But compared to CL or Racket, Python’s performance is a bitter pill to swallow.
PyPy won't be a candidate in some scenarios (nor is it always that much faster), and even libraries written in C leave some performance on the table when Python is the glue language. Don't get me wrong, I'm hardly ever in a position where Python limits me from a performance perspective ... there's always Cython. But it could be better.
They feel all limited to me :D I only did serious CL development but neither of those two especially attract me: they are less interactive (and that's my ideal and a daily joy), I don't need the Java ecosystem, I want to be lighter on resources… I take news about Red from time to time and it's still in its early phases it seems.
And as someone who programs in Clojure, I feel the interactivity is the same. Maybe I'm missing a detail of what he's doing though?
The part you don't have in Clojure (without using a library), is the restart options, but he didn't seem to ever use them, seems he just looked at the error message, and went back to fix the code and re-evaluate his test again, and that would be exactly the interactive flow I would follow in Clojure.
ah, I see. There's another shorter one below: https://www.youtube.com/watch?v=KsHxgP3SRTs In that one I get the debugger, I go to the erroneous function, I re-compile it, come back to the debugger and resume execution (seeing everything pass). Can you do that in Clojure? There's also the typical class change: in CL you can alter a class definition and the existing objects are (lazily) updated. Also (and it's like the only time I tried Clojure) you can't install a Clojure library from the Clojure REPL, right? And then, what's explained here: https://mikelevins.github.io/posts/2020-12-18-repl-driven/
I probably would need to play with that facility in CL to really get a sense of the difference.
In Clojure, you can debug at the REPL, but it doesn't drop you in the debugger by default. You'd need to eval the function as debug first, then it will drop you into the debugger next time the function is called.
And there's no restart system by default, so for example you probably wouldn't retry by choosing restart test from the exception debugger, you'd just re-evaluate the test yourself to have it rerun.
There's a condition restart library, and I played a bit with it, but not enough, I was confused about how to go about choosing options to handle the condition or how to define them.
So the typical flow in Clojure would be:
1. Error thrown
2. Go to line of code that threw error
3. Fix code
4. Eval fixed code
5. Eval the thing that threw the error again (seeing everything pass)
And if you want to observe the state at the place of the error thrown you'd add between 2 and 3:
2.1 Eval function as debug
2.2 Re-eval thing that threw error
2.3 When in debugger inspect all locals and state, find the problem
And then go to 3.
I never really looked into if there was a way to make eval as debug default with break on error, then I guess you'd get a very similar flow, except for missing the restart options.
> There's also the typical class change: in CL you can alter a class definition and the existing objects are (lazily) updated
Since Clojure isn't OO, this isn't a 1:1 comparison. But the closest thing to OO is polymorphic facilities, except they don't have state. Anyways, you can update the implementation of the polymorphic methods and it gets picked up for existing types or data that were polymorphic over them.
You can also add more polymorphic methods and those get picked up automatically.
There is a construct that has fields on it, but it's almost never used, so I actually don't remember if it would let you update fields on existing instances, I believe not. In CL can you add/remove fields on the class and the existing instances get updated? What happens to new fields how are they initialized in that case?
> you can't install a Clojure library from the Clojure REPL, right?
Well, you can, but let's say that it's something that easily breaks, so let's call it a "beta" feature. Some people use it effectively and don't seem to have issues, but others seem to always have it not work as expected, so a lot of people restart the REPL most likely when they want to add a lib.
Also, it's not a standard feature of Clojure, instead it's a feature of your build tool, either Leiningen or Tools.deps, and both have a different way to do it. Though tools.deps is the default dependency manager that comes with Clojure now, and its add-lib feature should one day get out of beta and become permanent, probably once they iron out the quirks.
> In CL can you add/remove fields on the class and the existing instances get updated? What happens to new fields how are they initialized in that case?
yes, and you can even specialize built-in methods so that the update does exactly what you want O_o See update-instance-for-redefined-class. It takes as arguments the added slots, the discarded ones, it's supposed to do the right thing but we can customize it.
Ah I see, ya that's a pretty neat feature for an OO language.
You wouldn't get that in Clojure for defining types, since they'd be Java types and then they only get what features the Java runtime allows on types and it doesn't have that one.
But in practice, since Clojure is functional, it's kind of a non-issue.
I should really try CL to be sure, but it seems you'd get 95% or more of the same interactivity in Clojure.
What I can tell you is I find Clojure superior to Emacs LISP in interactivity (which I've used both extensively). Emacs can technically redefine more things inside-out, like it can do the full runtime rebuild from the example link, but because it isn't functional and immutable first, managing the state as you redefine things tends to be more complex, and I find that makes it less interactive, since I need to do a lot of state setup and teardown to get things back to what I want to re-evaluate.
Admittedly I’ve never seriously investigated Clojure debugging tools so it might be that Clojure already supports something similar; but the Debugging Common Lisp[0] series might showcase how Common Lisp (with signals, restarts, mechanisms for redefining classes, p and more…) and SLIME aids debugging in how other ‘usual’ languages don’t.
By using Clojure, you’re missing out on reliance on poorly maintained ffi libraries, a messy library ecosystem where you get randomly surprised by side effects from mutability where you weren’t expecting it, and a poor build system.
I love writing CL but there’s a reason it’s not enjoying wide usage nowadays, and I definitely wouldn’t go around recommending it to unsuspecting people without heavy caveats.
Common Lisp is great, I'm sure, but it's a bit of a nonstarter for me because most of my work is Spark ETL and machine learning. I can theoretically sneak some hy in for low-visibility code but I can't do the same for Common Lisp.
> no closures in Hy last time I checked
I don't know when the last time you checked was, but as far as I can tell hy definitely has closures. It largely follows Python semantics, after all.
> This was primarily for the lack of good parallel, concurrent garbage collectors in Common Lisp implementations. The CL version of elPrep was actually still a tad faster than any of the C++, Go, or Java versions, but we had to work hard to avoid long GC pauses. elPrep allocates a lot of memory, and the pause time hurts a lot. We solved this by, basically, disabling the garbage collector, and reusing memory manually as much as possible, which turned the program into almost a manually memory-managed affair.
With respect to concurrency, after a lot of experience operating async Python services, the thing that I look for is a single, standard, ubiquitous concurrency model. One of the biggest pain points for operating Python systems is some transitive dependency making a synchronous I/O call and blocking the whole event loop, causing timeouts in unrelated endpoints and often bringing the whole system down (healthchecks start to fail because the endpoint is blocked and then the supervisor kills the whole process/pod/etc).
This doesn't happen in Go or (presumably) languages that run on Erlang's BEAM VM because there is no sync I/O (from the event loop's perspective, anyway).
Python has pre-emptive threading too! The GIL makes it all but useless for CPU-bound tasks, but it works fine for IO. Keep your health-check tasks in a non-IO thread and communicate with queues.
> What I look for in a language first is how good the support is for concurrency, and especially if it has a good, modern concurrent garbage collector.
...in which case you'll definitely not want to use Python either, and so are rather far out of place in this thread.
Python is a great language, but its design-mistakes regarding concurrency are exactly why I'm worried about that aspect when looking for a new language.
Which CL "interactive development" feature can't you get with either a jupyter notebook or ipython + autoreload enables? With a smart editor like VSCode or Spyder you also get visual variable inspections, history of executed code in one way or another etc. Only that can't easily save an "image" auto-magically, but data science and ML people wouldn't want to do this other than explicitly (eg. using fast loading formats like feather to reload semi-processed vars etc.).
I don't think you are aware how much the Python workflows of AI / ML / datasci people resemble you "common lisp ideal" ...but with a different language and better libraries :)
And if Julia succeeded, it will also bring decent performance to such workflows.
Sure, the way backend devs or devops people use Python is very static and different, but any experienced ML practitioners are at home with "interactive development".
Re-define a class and have all objects automatically get updated to reflect the changes.
Evaluate arbitrary expressions mid-stack trace.
Run into a bug, get caught in an error, re-define the offensive function interactively, and re-run the computation before that point, all without stopping and restarting.
(Example real-world use-case: You re-define one of your REST API endpoints without having to restart your server or interrupt the request-flow, be it in test or—if you so desire—in prod.)
Change the class of an object at run-time.
Re-compile and re-load individual functions from your editor with your production, git-committed code. This one is huge. You use interactive and incremental development to, well, develop! Not side-by-side with your code, but in your code as well, at a fine-enough grained level.
Common Lisp was designed to be both interactive and incremental. It's not a bolt-on feature.
Jupyter notebooks and auto-reloading have a very different feel, and (dis)incentivize different qualities and processes of development.
Basically all you mentioned above can be done in modern Python.
Unfortunately I've seen it done... and God, the results are horrible, in Python. "Wait did calling this method just change the class of the object the method was on??!!!! Fuuuu..."
But simple stuff like incrementally building classes method by method in eg. jupyter notebooks can be done and it's even quite clean, it's just that nobody is teaching people the techniques for doing it... (And the jupyter autoreload magic doesn't seem to like it so you'd have to "do it by hand")
And sure, since Python got scoping and concurrency quite wrong, if you go full-interactive on things you'll shoot yourself in the foot faaast!
This set of jupyter + autoreloads + editor features might get you close, but you won't be on par to CL's level. I'm thinking about CL's interactive debugger, and about language features that were thought with interactivity in mind. On an error, you get the interactive debugger, you can explore the stack frames and get to the erroneous function, change it, recompile it, come back to the debugger and ask it to resume progress from this point, and see execution pass. You didn't have to re-run the whole operation. If we can do that in Python, I've been in my cave for too long^^ As such, this is how debugging a complex program looks like in Python vs in CL: https://lisp-journey.gitlab.io/images/debugging-python-VS-li... (Ravenpack [1] is a company that does big data analysis and that uses CL, they gave some talks, maybe you'll find more practical reasons from them)
There might be more CL libraries than you think: https://github.com/CodyReichert/awesome-cl but I'm not that a zealot to say you can ignore Python's ecosystem, especially in certain areas…
> If we can do that in Python, I've been in my cave for too long^^
...practically speaking you're kind of right, you (almost) can't.
In theory one can use `sys.excepthook` to start the python interactive debugger on error and/or %debug jupyter magic, but the "change it, recompile it, come back to the debugger" is not really possible unless you the yuuuuucky thing of changing it from the interactive debugger itself (think change def in code, paster changed def in interactive debugger).
As a python expert you can do it. Even modify classes at runtime etc. Tried at various times doing the various things mentioned above. But tbh you learn to rarely do it because you easily get in a twilight zone hell where it's impossible to reproduce anything, no idea what state of code is "active" and what your functions and variables actually are :)
...but basically what Python lacks to enable proper implementations of such feature is decent concurrency - while a thread is paused at the breakloop, the interpreter should allow another to eg. execute code that updates functions and classes definitions - all Jupyter's magic can't do this bc concurrency in the Python interpreter is not doable :)
...in theory the Julia jupyter kernel for example could actually implement all the features mentioned by people describing CL loops - just that someone would need to put the tons of work in, and unfortunately Julia folks seem focused on the automagic-reactive approach to recreating excell-but-as-a-notebook (I don't like this magic-reactive approach tbh... try doing that and have a random variable change "accidentally" triggering recomputing something with a 10gb dataframe you had in var or the retraining of a big deep learning model).
I don't think it's fair that you got down-voted for not knowing what Lisp-style interactive development offers beyond what ipython or jupyter offers. Many people don't know either and the proper response would be to explain, not to down-vote.
EDIT: I was made aware of the guidelines to not comment on voting, I apologize.
In addition to the comprehensive answers of reikonomusha and vindarel, I'll give you some specific things you can't do:
Jupyter notebook: you can't run it as normal code - that is, you can't import your notebook from another file. You also can't evaluate arbitrary regions of the notebook (it's cells, the whole thing, or nothing), or nested sub-expressions. You can't evaluate code in parallel - if you have a cell calling a function foo() in a loop, and you try to redefine foo() in another cell - impossible in Juypter, trivial in CL.
iPython + autoreload: you also can't redefine a foo() in a file when it's being called in a loop, docs state "reloads modules automatically before entering the execution of code typed at the IPython prompt". You don't get any sort of interactive debug-and-fix-up when you encounter an error. And so on.
For the record, I have written a reloading library for Python called jurigged[0] that works on normal Python code and can indeed replace foo() when it's being called in a loop. There is also a way to do debug-and-fix-up, but only if you specify restart points in advance, unfortunately.
It is probably still a good deal more limited to what can be done with CL, there are no miracles here, just saying the gap may not be that big.
This reads out of touch with why curious people use Hy, like me who wanted to get going with an easily accessible language and ecosystem (Python) while rewriting bits in Hy to grok some lisp magic.
No, of course I wouldn't have reached for CL first and it's disappointing to read the top-comment is this.
But... by using Hy you're already learning a new super niche language. If you're going to learn a new language why not ditch Python and use something good?
Ecosystem size is really the only thing I can think of.
Because I write Python to plug into a bigger ecosystem and I can mix and match Python and Hy freely. I can also use Poetry to package and manage dependencies and Poetry2nix to output a Nix derivation that is guaranteed to run on someone else's machine. I can also freely tap Python packages like Beautiful Soup or use frameworks like Django (which I am). The entire Python universe is available to me. The number of accessible tutorials for Django, videos for Pythonistas and community support channels is monumentally large. I even get to do cool things like use Django's ORM for learning how to interact with and manipulate data in Postgres, all right there available to me.
I can use Discord (despite my distaste for it as a platform it has a huge Django and Python community), Telegram, IRC, and Matrix to ask for help and get metric tonnes of friendly advice, suggestions and criticisms from Pythonistas.
And I can take all of this at my own pace, writing only bits in Hy and branching as I go. Every step is as gradual as I want it to be.
Common Lisp is not the answer to my needs. Presenting it as so is tone deaf at the least.
but who's presenting CL as the answer to your needs? (me?)
If you're happy with your mix of Python and Hy, well, great. But you know what, you can do what you described with CL too! Need to do some webscraping? Beautiful Soup -> lquery. Django -> well… no CL framework gives you all these batteries included, but what if you want to extract data from a Postgres DB and display it in a web app? that's very easy [1] in CL too. Django ORM -> postmodern or Mito (or more). IDK about Nix but several people are working with CL and Guix.
What worked out for me was to write companion services in CL, instead of extending my big Django app. This new service that takes data from a FTP server, transforms it, stores it, displays it in a web app? CL. That web app that serves another kind of customers than the Django one? A CL app. It isn't for everybody and sometimes you wish for a bigger ecosystem, but anyways, many things can be done in CL.
[1]: ok honestly the learning process was not that easy. It's a bit easier with my contributions now! (Cookbook etc)
I think you're misunderstanding the appeal of ecosystems here. It's not just about install base - it's about the mental load of adjusting to a new set of assumptions and paradigms for interacting with the computer.
To me, the way to think about "introducing" someone to Lisp and helping them understand the choices the language makes - you really want to focus on the things that make Lisp stand out, and one way of doing that is re-using a lot of the functionality of a language that outsiders may be familiar with.
I generally agree that if you want to build something substantial you probably would choose CL over Hy, but if you want to "try a lisp" while minimizing startup costs and you have a Python background I think Hy makes way more sense.
On top of that, just figuring out which Lisp to start with, and how to get started with it, is an ordeal. In the JVM ecosystem, Java is pretty obvious, and Kotlin is a pretty obvious alternative. In the Lisp ecosystem, the well known SCIP is targeted at Scheme, Clojure is pretty well known, then there's Racket, ChezScheme, CommonLisp, and the they all look like pretty good starting places.
It's an expensive choice to make, too. It quickly becomes apparent that, no matter which lisp you choose, there will always be a group of people trying to convince you that you picked the wrong one, and pressuring you to second-guess yourself.
It's expensive because seeing that kind of stuff as a potential newcomer to a community can have a way of psyching a person out and making them think they need to be extra careful about this decision.
Basically, that "if you choose..." advice is for the wrong context, specifically because of the "if you choose" qualifier. I'm talking about what happens during the pre-choice time period.
I don't want to diminish the challenges newcomers have with interacting with people online, but we have to understand that even choosing Lisp is going to be met with significant derision from people who don't write Lisp since it's not a top-10 language. "Lol just use Python it's the second best at everything," is a common refrain to people who are trying in earnest to learn something new and nonstandard for a change.
While I won't claim nobody has been "psyched out" by a community of people for their language-learning choices, I just don't think that this is something that happens with Lisp particularly often. The subreddit r/Lisp is predominantly Common Lisp, but more frequently than not accepts viewpoints from Clojure, Scheme, Racket, and even NewLisp programmers all in the same comment section, without bickering.
As I said, if anybody wants to
learn a new technology, and that somebody is a social person, finding a supportive community is important. If somebody going to be a novice at a particular language, and broadcast opinions about that language on disparate forums—some opinions which may come across as uninformed—sure, people will likely come out of the woodwork to correct them, suggest allegedly superior technology, etc.
I don't want to devolve into any petty arguments about what makes a Lisp a "Lisp", since everybody has their own incompatible definition, but I will say writing Hy feels like writing Python with weird syntax, and it does not feel like writing Lisp with a huge, familiar ecosystem of libraries.
I will give you this, though, you can have a taste macros with Hy, which of course are one of the cornerstones of different Lisps.
With that said, I do not feel like one would accurately develop a feel for what Lisp is like (be it Common Lisp, Scheme, Racket, or Clojure) by using Hy.
> I don't want to devolve into any petty arguments about what makes a Lisp a "Lisp",
Very much agree! I shouldn't have framed it that way.
I should have said: each time you make a hybrid language between approaches, there are new people who will try it out and may discover a new and exciting way to program. Some people, in hindsight, will certainly wish they had jumped further - but they can always do that in the future and some will only make the bigger jump after making a smaller one.
There are two “ecosystems” at topic here: the language you’re writing code in, and the environment that it’s being executed in. In this case, you’re writing Hy that’s executed in a Python language environment, that allows you to tap into its entire ecosystem with Hy (in a similar way like Clojure and the JVM). These types of “alternative front ends” are a great way to bridge the gap for a new language.
As such, for Hy’s target audience, I think it’s not as much “ditch a new language” as it is “I have to use Python for $reason, but I want something more like CL”. A good example of this would be any machine learning related work.
Last but not least, I would expect that on HN we focus a bit more on “what could make Hy even better” rather than “what other languages are better than Hy”.
Can you elaborate on how CL provides more static type checks ? I am not much familiar with CL, but does it provide something similar to typed-racket ? Do major libraries actually use it ?
Not so easy question, especially because it depends of the implementation, but SBCL gives pretty good static type errors and warnings. Mostly, you get warnings at the function boundaries. It isn't complete like type checking in the modern languages, but it helps catch many errors. And quickly & interactively, because we compile our functions as we write them, with a keystroke (or the whole file, and from time to time, we build the whole project from scratch). So, coming from Python, that was superbly useful to me. This, everybody uses it. We can also add gradual typing to our variables and functions. It might help for the static checks, and it also gives hints for the compiler to speed things up. We can also declare our own types, but these won't be used for compile-time inference.
Take this with a grain of salt, because I’m neither a user nor expert of Typed Racket, but:
Typed Racket focuses more on the gradual typing of a given program. It has lots of features to make that easier, such as occurrence typing. Coalton is a separate, embedded language.
Typed Racket achieves polymorphism through subtyping and and first-order type variables. Coalton achieves polymorphism through type variables, higher-kinded types, and type classes.
Coalton, like ML and Haskell, focuses on defining objects by their properties and supported functions. This is a proposed way of having modular, reusable code. Typed Racket, as far as I can tell, has no such features.
Coalton code can be fully inferred, so type annotations are not necessary. Typed Racket cannot.
All in all, I think the biggest and most important take-away is that Typed Racket goes through great effort to seamlessly blend with ordinary Racket. But that means Typed Racket has to compromise on type system features that can only be supported if you’re willing to change the language itself.
Coalton puts the type system first, opting for something close to Haskell, at the expense of not being a system for gradually typing Common Lisp, and instead being a separate language altogether.
---
They use it for their open-source quantum compiler and other things.
possibly because i've been playing with Guix lately, and also because all the smartest people I know seem to use lisp-likes, I'm on team "just use a Lisp" (possibly with some sort of API interface to Python)
Have they ever fixed the problem with let not being implemented (which was the case when I looked at it a couple of years ago). Lisp-like languages without let lose a lot of the inherent power of lisps.
It's sort of back, implemented as a macro. I've yet to go back to Hy (I left because of the removal of let, plus other things I found missing), but I try it every now and then and the last time I did the namespacing was still a tad weird.
It's been implemented, found to be subtly broken, and re-implemented a number of times. Ultimately trying to impose a different model of scoping on top of Python's scoping is living in a state of sin; thus the difficulties. The status quo is an apparently working `let` due to a truly heroic effort by Sunjay Cauligi ( https://github.com/hylang/hy/pull/2125 ). You're encouraged to try to find bugs in it.
In truth, I've never understood the fuss about `let`. It's taken a huge amount of Hy development effort, but it adds very little. Python's native scoping works well in the large majority of cases, and in tricky cases, I'd rather use `nonlocal` or `global` explicitly so I'm sure what's happening at the level of Python semantics. I suspect that people who heard that previous versions of Hy were missing `let` assumed that some other feature (such as nested lexical scopes) was also missing that has actually always been there.
you probably `2` to be printed, but the seeming equivalent Python code
a = 1
def foo():
a = 2
foo()
print(a)
prints `1`. Python interprets the assignment as declaring a new local variable. Frankly, Python got scoping wrong (in its quest to eliminate declarations), but here we are. So `let` needs to work around this somehow. The current implementation implicitly adds `global a` or `nonlocal a` as appropriate. (Actually, the name `a` is also mangled so that any references to `a` outside the `let` refer to a different variable.)
I'm not sure this is so much of a problem? Changing what you name the variable fixes the issue.
Python is generally quite good at having a verbose option to express things, so I'm surprised there's not an explicit way to reference the enclosing scope from an object.
For me the example printing `1` makes sense because I think `let` as being something immutable, so I wouldn't expect `setv` to mutate that variable (would expect either a compiler error or the introduction of a new local variable).
But maybe it is because I am familiar with Clojure (where `setv` or equivalents doesn't exist) and not other Lisps.
> In truth, I've never understood the fuss about `let`. It's taken a huge amount of Hy development effort, but it adds very little
My use case is Jupyter notebooks. With normal Python scoping you quickly accumulate a lot of global variables and you might accidentally use the wrong one. With let-bindings you have variables that clean up after themselves.
I'm not familiar with Jupyter internals, but I think functions would work well for this. Hy allows multi-statement anonymous functions, and you can do stuff like `((fn [] … ))` to define and immediately call the function. One advantage of this over `let` is that it allows old locals to be garbage-collected. If memory isn't an issue, though, `let` should work well.
They are working on improving it. I wrote a short little book on Hy and I initially used the “let” macro. I stopped using it because in general Hy code can be automatically translated to very readable Python code, but using the “let” macro generates ugly Python code.
Does it support some kind of repl driven development similar to lisp/clojure? Ie evaluate expressions on a live app and update the live up with the new values?
The experience I had with it the last time i tried it was that it was syntactically very similar to Clojure, but certain subtle differences with it. Close but not being precisely same as something else could often result in confusions.
I've been trying to steer the syntax towards imitating Python more than Clojure, the idea being that you frequently need to rapidly switch between reading Python and Hy (as when you're writing a Hy program while consulting documentation for a Python library), so it helps to minimize friction there. Thus e.g. `True` must now be capitalized that way, Python-style; `true` used to be allowed as well. But whether a more Pythonesque Hy is more or less confusing than a more Clojuresque Hy is admittedly a matter of taste.
I completely understand the decision and applaud the fact that Hy seems to be going of a direction of being a much finer layer than it was previously (removing things from Hy itself and moving to Hyrule, for example).
On the other side I am quite torn about the decision. I quite liked the early releases of Hy where I could write a code that was much more familiar for me as a Clojure programmer. Still need to give a try to the new releases though.
The first snippet of code I see on that page requires me to understand way too much syntax. It's nice to see smaller snippets of code when first exploring a new language to decide if it's worth the effort to learn the syntax and language features.
Not really. I want to understand why the language is interesting. For instance, even though I know nothing about Lisp, I can guess that it supports booleans, integers, and floats. Tuples and complex numbers are kind of interesting. But then it goes on to strings and concatenation, which I already assumed were possible in Lisp.
The Hy page had way too much syntax, but the Hissp Quick Start didn't have enough? Or not soon enough? One does have to understand the little pieces to make sense of the big ones. You want something in between? Can you point to an example at the right level?
Ctrl+F and try skipping to ";;;; The Basic Macros" section on that quick start page. Is that about the right level?
For many of the "popular" languages, a simple cheat sheet showing the equivalent control structures in a common language might be enough for a typical programmer to get a handle on it quickly. But Lisp is not an Algol-family language. It's different enough that such a document would fail to convey what makes it interesting. There's an inferential gap that must be bridged first, and it's difficult to do that quickly; a programmer mistakenly [expecting a short inferential distance][1] may not have the patience to "get it".
Using Go as an example, if I were to write a "Why Go" article, it might look something like this:
1) Simple, one way to do everything. for loops, code formatting, and project setup.
2) Good concurrency (go routines)
3) No exceptions, readable code and error handling.
This article is titled "Why Hy." It should sell me on the language with a philosophy supported by examples, not instruct me on how to write a floating point number (unless the language has a interesting take on writing floating point numbers!)
In that case, would the Hissp README https://github.com/gilch/hissp#readme be an appropriate introductory article in your view? It opens with some small examples and concludes with some design philosophy.
Hey! I'm the author of Basilisp. Thanks for the mention here!
Basilisp is much less mature than Hy but tries to stay generally compatible with Clojure, which is somewhat different than the goals of the Hy project.
Cool, I'm a Hy guy; glad to meet you. Hy in its early years imitated Clojure syntax much more closely, and Clojure compatibility may've been implicitly a goal even if it never got to the point of cross-testing. These days we prefer to think of Clojure as one of several possible influences.
Hy is a false promise. It looks like a lisp; depending on your idea of a Platonic lisp, it may be a lisp --- but it preserves Python semantics, including its odd scoping rules, and it doesn't feel like writing actual lisp. Scheme or Common Lisp simply feel more coherent, having semantics that make sense with the language.
I've been working on Hy since 2016 and I've frequently encountered, but never understood, the sentiment that Hy isn't a "real Lisp". What makes for a real Lisp? There have been dozens (hundreds?) of Lisps over the decades, and if they all have a defining characteristic that Hy lacks, I don't know what it is. Certainly Lisps have varied in how scoping works, among other things.
Agreed. If odd scoping rules are the problem, we can possibly disqualify most lisps that have ever existed. Including all of the ones that John McCarthy had a hand in building.
For my part, I had similar "this doesn't feel quite like lisp" moments with both Hy and Clojure. And I ultimately realized that it's the mere presence of the base platform that gets me. With Racket, I'm used to feeling like I'm sealed off in my own lispy bubble. With Hy and Clojure, there are always bits of Python or Java (read: Not Lisp) hiding in the background. Many differences from other well-known lisps that I know are attributable to that base platform.
And that's a catch-22 situation. Objectively, they might be (and, IMO, are) good design decisions. But they're also covered in non-lisp cooties. I don't know why I should expect otherwise. Brains are weird.
I haven't gotten into Hy, but what you say about a 'false promise' of lisp reminds me of the impression Fennel left me. No cons, compile error for '(), no quasiquoting.. it left me wondering where the list processing was.
Fennel made a really interesting choice in rigidly sticking to the lua semantics.
I think in isolation it would be a fairly bad choice, but in practice you get a drop-in replacement for lua that fixes every problem with lua.
I wouldn't want any other lisp to work like it does but damn I consider that thing my savior at this point. I've come to really hate lua and fennel bails me out every time.
I suppose I should give Fennel another chance, since I have similar feelings towards Lua. Lack of quasiquoting particularly does sting though, using quasiquoting to construct lua tables seems like it would be nice.
There's also that partial implementation of the clojure stdlib in fennel that might have it. If not... it does have a macro system though I've avoided using it. Macros can be a quagmire even in popular well-documented lisps which fennel definitely is not either.
So, Hy allows "to freely mix imperative, functional, and object-oriented styles of programming."
I wonder: 1/ Shall we mix multiple paradigms of programming in the same project? 2/ Should we adopt a single programming language that supports multiple programming paradigms and utilise single paradigm for each given project, for the sake of minimizing the skill set required to the development team and – not necessarily –the learning curve?
and any time a language allows this "free mixing", it loses the very tangible guarantees of purely-functional languages such as immutable data, emphasis on no side effects, etc.
I definitely had fun writing that in Emacs/hy-mode. And indeed having access to Python ecosystem is neat. However, if I may:
- I didn't get to use any intellisense, which was quite painful. Is there any Language Server for Hy now?
- I think Hy tends to break with every Python minor release, which is a bit annoying. Stable is still broken on 3.10, and alpha is a big change.
- This one might be a matter of subjectivity but I felt that Hy is trying to be more "pythonic" and less "lispy", and I am not sure what to feel about that. For example, familiar things like `&kwargs` or `&optional` seems to have got replaced with something less familiar (particularly, change to `#**` for keyword arguments spurred this though).
2. That's right. Hy depends much more on finicky details of Python than most Python packages do, particularly Python's AST, so each 3.x release of Python usually requires some changes to Hy to support it. Each Hy release supports (at least) all versions of Python 3.x that have been fully released and are not yet at end of life.
Hissp is an alternative approach. Unlike Hy, which compiles to Python AST, Hissp compiles to Python, so it doesn't break with every Python release.
Its read language (Lissp) doesn't have an Emacs mode (yet), but its syntax is so close to traditional Lisps that lisp-mode pretty much works. No completions either. There is Lily https://github.com/mjobuda/damascus-tools/tree/main/lily, which proved Jedi works on Lissp, but it's never quite worked right.
Hissp leans more Lispy than Pythonic compared to Hy, but still prioritizes interop, so the object model is still Python's.
Most of python system i've seen is full of bugs, lack of stability, which causes huge damage from developer resources as well as operational costs. It's too hard for a junior Python developer to produce a reliable system.
One of the reason as i know is, they have no idea of using unit test.
Looks pretty neat, for the LISPy use cases out there.
I think metaprogramming is a bit of an antipattern in most use cases(The more power the programmer has, the less the compiler/linter can follow it, just because... there's more possibilities).
But for the stuff it that needs it, it seems to be unbeatable, and this seems to have type annotations, which fixes one of traditional lisp's ickiest things.
Plus, having access to Python's libraries should make it possible to really evaluate what LISP can do by as a language, rather than having the choice mostly made by ecosystem size for a lot of projects.
I prefer a much more predefined structure that fits in better with autocomplete, and focuses more on reuse than new ideas, but I could see some pretty amazing stuff being done in this, and it being a benefit to the Python community as well.
Not sure I follow the logic. That sounds like running non-repeatable unit tests by hand.
You don't really have any help from the system, it's all on you to ve sure you've tested every code path, and it doesn't produce any formal report like a linter's "No warnings" message.
Nor can other devs repeatably do the same tests and get a list of warnings to work on, you can't automatically test the whole existing codebase when new static checking comes by, etc.
It's super useful for experimenting when you aren't sure of something, and it's great to have a debug console in the debugger, but I've never been that much of a fan of interactive programming in most cases.
Plus, the same annotations used for static analysis also drive modern intellisense type tools, so even the most quick casual project can still have some pretty great documentation with almost no time cost.
I always thought the main reason lisp did not become the world's dominant language (or, at least, dominant language with garbage collection) is that writing
(/ (+ (- b) (sqrt (- (* b b) (* 4 (* a c)))) (* 2 a))
is a pain in the neck, and even more so when you have to read it, while
(-b + sqrt(b*b - 4*a*c))/(2*a),
can be checked on the fly.
I am kind of amazed this hasn't finally sunk into programmers' hearts.
> I always thought the main reason lisp did not become the world's dominant language (or, at least, dominant language with garbage collection) is that writing
I can write ugly code in any language. But if I was writing this code in Clojure, I would probably do something like this:
(let [r1 (- (* b b) (* 4 a c))
r2 (+ (- b) (sqrt r1)))
r3 (* 2 a)]
(/ r2 r3))
That is perfectly readable to me.
Yeah, you may say that I needed to introduce other variables to make this work and the code got bigger as a result, but on the other side how many people are actually writing this kinda of code on daily basis that are not Computer Scientists?
(/ (+ (- b)
(sqrt (- (* b b)
(* 4 (* a c))))
(* 2 a))
The trick is that a trail of any number of `)`'s should also end the line. Like any language, it takes some getting used to, but I find this tree structure perfectly readable now.
Sure for the four-function calculator operations, operator precedence levels are a reasonable notation, but when you can define your own functions, and your programming language now needs a dozen precedence levels, it becomes hard to read. Lisp is completely unambiguous.
Python expressions are accessible in [Hissp][1]. In [readerless mode][2], you're writing in Python anyway, so it's trivial. In [Lissp][3], you can inject Python code strings with `.#`. In [Hebigo][4], you just put parentheses around the whole thing.
130 comments
[ 0.20 ms ] story [ 210 ms ] threaddoom emacs make it easy to get started.
So if you design a language and want to use Lisp-like syntax, I recommend to also check out io before steaming ahead.
Though I have to admit I can not imagine how a person can be so mentally inflexible as to be not able to tolerate Lisp-style syntax. I get that Common Lisp might look a bit old-school at first glance but something like Clojure is basically designed to look aesthetically pleasing.
In the case of most Lisps, code is written using the syntax of list and symbol literals. (There are other literals too, of course.)
is a nested list literal with four symbols. You can think of symbols as "interned strings"; they're written without quotes for convenience. It's not unlike in Python, except I had to use actual strings instead of symbols because Python doesn't ship with a standard symbol type.This is also a nested literal list:
but this happens to be a list literal that can be "run" (or in Lisp parlance, evaluated) as code.And as someone who programs in Clojure, I feel the interactivity is the same. Maybe I'm missing a detail of what he's doing though?
The part you don't have in Clojure (without using a library), is the restart options, but he didn't seem to ever use them, seems he just looked at the error message, and went back to fix the code and re-evaluate his test again, and that would be exactly the interactive flow I would follow in Clojure.
Am I missing something?
In Clojure, you can debug at the REPL, but it doesn't drop you in the debugger by default. You'd need to eval the function as debug first, then it will drop you into the debugger next time the function is called.
And there's no restart system by default, so for example you probably wouldn't retry by choosing restart test from the exception debugger, you'd just re-evaluate the test yourself to have it rerun.
There's a condition restart library, and I played a bit with it, but not enough, I was confused about how to go about choosing options to handle the condition or how to define them.
So the typical flow in Clojure would be:
And if you want to observe the state at the place of the error thrown you'd add between 2 and 3: And then go to 3.I never really looked into if there was a way to make eval as debug default with break on error, then I guess you'd get a very similar flow, except for missing the restart options.
> There's also the typical class change: in CL you can alter a class definition and the existing objects are (lazily) updated
Since Clojure isn't OO, this isn't a 1:1 comparison. But the closest thing to OO is polymorphic facilities, except they don't have state. Anyways, you can update the implementation of the polymorphic methods and it gets picked up for existing types or data that were polymorphic over them.
You can also add more polymorphic methods and those get picked up automatically.
There is a construct that has fields on it, but it's almost never used, so I actually don't remember if it would let you update fields on existing instances, I believe not. In CL can you add/remove fields on the class and the existing instances get updated? What happens to new fields how are they initialized in that case?
> you can't install a Clojure library from the Clojure REPL, right?
Well, you can, but let's say that it's something that easily breaks, so let's call it a "beta" feature. Some people use it effectively and don't seem to have issues, but others seem to always have it not work as expected, so a lot of people restart the REPL most likely when they want to add a lib.
Also, it's not a standard feature of Clojure, instead it's a feature of your build tool, either Leiningen or Tools.deps, and both have a different way to do it. Though tools.deps is the default dependency manager that comes with Clojure now, and its add-lib feature should one day get out of beta and become permanent, probably once they iron out the quirks.
https://practical.li/clojure-staging/alternative-tools/cloju...
yes, and you can even specialize built-in methods so that the update does exactly what you want O_o See update-instance-for-redefined-class. It takes as arguments the added slots, the discarded ones, it's supposed to do the right thing but we can customize it.
You wouldn't get that in Clojure for defining types, since they'd be Java types and then they only get what features the Java runtime allows on types and it doesn't have that one.
But in practice, since Clojure is functional, it's kind of a non-issue.
I should really try CL to be sure, but it seems you'd get 95% or more of the same interactivity in Clojure.
What I can tell you is I find Clojure superior to Emacs LISP in interactivity (which I've used both extensively). Emacs can technically redefine more things inside-out, like it can do the full runtime rebuild from the example link, but because it isn't functional and immutable first, managing the state as you redefine things tends to be more complex, and I find that makes it less interactive, since I need to do a lot of state setup and teardown to get things back to what I want to re-evaluate.
[0]: https://malisper.me/category/debugging-common-lisp/
I love writing CL but there’s a reason it’s not enjoying wide usage nowadays, and I definitely wouldn’t go around recommending it to unsuspecting people without heavy caveats.
(Common Lisp is super cool, though. Perhaps one day Hy will be as cool.)
> no closures in Hy last time I checked
I don't know when the last time you checked was, but as far as I can tell hy definitely has closures. It largely follows Python semantics, after all.
MGL's auhor [won](https://github.com/melisgl/higgsml) the Higgs Boson ML Challenge.
A quick search brought up this:
https://lisp-journey.gitlab.io/blog/pro-mailing-list-on-comm...
> This was primarily for the lack of good parallel, concurrent garbage collectors in Common Lisp implementations. The CL version of elPrep was actually still a tad faster than any of the C++, Go, or Java versions, but we had to work hard to avoid long GC pauses. elPrep allocates a lot of memory, and the pause time hurts a lot. We solved this by, basically, disabling the garbage collector, and reusing memory manually as much as possible, which turned the program into almost a manually memory-managed affair.
This doesn't happen in Go or (presumably) languages that run on Erlang's BEAM VM because there is no sync I/O (from the event loop's perspective, anyway).
...in which case you'll definitely not want to use Python either, and so are rather far out of place in this thread.
I don't think you are aware how much the Python workflows of AI / ML / datasci people resemble you "common lisp ideal" ...but with a different language and better libraries :)
And if Julia succeeded, it will also bring decent performance to such workflows.
Sure, the way backend devs or devops people use Python is very static and different, but any experienced ML practitioners are at home with "interactive development".
Evaluate arbitrary expressions mid-stack trace.
Run into a bug, get caught in an error, re-define the offensive function interactively, and re-run the computation before that point, all without stopping and restarting.
(Example real-world use-case: You re-define one of your REST API endpoints without having to restart your server or interrupt the request-flow, be it in test or—if you so desire—in prod.)
Change the class of an object at run-time.
Re-compile and re-load individual functions from your editor with your production, git-committed code. This one is huge. You use interactive and incremental development to, well, develop! Not side-by-side with your code, but in your code as well, at a fine-enough grained level.
Common Lisp was designed to be both interactive and incremental. It's not a bolt-on feature.
Jupyter notebooks and auto-reloading have a very different feel, and (dis)incentivize different qualities and processes of development.
Unfortunately I've seen it done... and God, the results are horrible, in Python. "Wait did calling this method just change the class of the object the method was on??!!!! Fuuuu..."
But simple stuff like incrementally building classes method by method in eg. jupyter notebooks can be done and it's even quite clean, it's just that nobody is teaching people the techniques for doing it... (And the jupyter autoreload magic doesn't seem to like it so you'd have to "do it by hand")
And sure, since Python got scoping and concurrency quite wrong, if you go full-interactive on things you'll shoot yourself in the foot faaast!
for the second point: https://mikelevins.github.io/posts/2020-12-18-repl-driven/
There might be more CL libraries than you think: https://github.com/CodyReichert/awesome-cl but I'm not that a zealot to say you can ignore Python's ecosystem, especially in certain areas…
[1]: https://www.ravenpack.com/
...practically speaking you're kind of right, you (almost) can't.
In theory one can use `sys.excepthook` to start the python interactive debugger on error and/or %debug jupyter magic, but the "change it, recompile it, come back to the debugger" is not really possible unless you the yuuuuucky thing of changing it from the interactive debugger itself (think change def in code, paster changed def in interactive debugger).
As a python expert you can do it. Even modify classes at runtime etc. Tried at various times doing the various things mentioned above. But tbh you learn to rarely do it because you easily get in a twilight zone hell where it's impossible to reproduce anything, no idea what state of code is "active" and what your functions and variables actually are :)
...but basically what Python lacks to enable proper implementations of such feature is decent concurrency - while a thread is paused at the breakloop, the interpreter should allow another to eg. execute code that updates functions and classes definitions - all Jupyter's magic can't do this bc concurrency in the Python interpreter is not doable :)
...in theory the Julia jupyter kernel for example could actually implement all the features mentioned by people describing CL loops - just that someone would need to put the tons of work in, and unfortunately Julia folks seem focused on the automagic-reactive approach to recreating excell-but-as-a-notebook (I don't like this magic-reactive approach tbh... try doing that and have a random variable change "accidentally" triggering recomputing something with a 10gb dataframe you had in var or the retraining of a big deep learning model).
EDIT: I was made aware of the guidelines to not comment on voting, I apologize.
From the HN guidelines at https://news.ycombinator.com/newsguidelines.html
Jupyter notebook: you can't run it as normal code - that is, you can't import your notebook from another file. You also can't evaluate arbitrary regions of the notebook (it's cells, the whole thing, or nothing), or nested sub-expressions. You can't evaluate code in parallel - if you have a cell calling a function foo() in a loop, and you try to redefine foo() in another cell - impossible in Juypter, trivial in CL.
iPython + autoreload: you also can't redefine a foo() in a file when it's being called in a loop, docs state "reloads modules automatically before entering the execution of code typed at the IPython prompt". You don't get any sort of interactive debug-and-fix-up when you encounter an error. And so on.
It is probably still a good deal more limited to what can be done with CL, there are no miracles here, just saying the gap may not be that big.
[0] https://github.com/breuleux/jurigged
No, of course I wouldn't have reached for CL first and it's disappointing to read the top-comment is this.
Ecosystem size is really the only thing I can think of.
I can use Discord (despite my distaste for it as a platform it has a huge Django and Python community), Telegram, IRC, and Matrix to ask for help and get metric tonnes of friendly advice, suggestions and criticisms from Pythonistas.
And I can take all of this at my own pace, writing only bits in Hy and branching as I go. Every step is as gradual as I want it to be.
Common Lisp is not the answer to my needs. Presenting it as so is tone deaf at the least.
If you're happy with your mix of Python and Hy, well, great. But you know what, you can do what you described with CL too! Need to do some webscraping? Beautiful Soup -> lquery. Django -> well… no CL framework gives you all these batteries included, but what if you want to extract data from a Postgres DB and display it in a web app? that's very easy [1] in CL too. Django ORM -> postmodern or Mito (or more). IDK about Nix but several people are working with CL and Guix.
https://github.com/CodyReichert/awesome-cl
What worked out for me was to write companion services in CL, instead of extending my big Django app. This new service that takes data from a FTP server, transforms it, stores it, displays it in a web app? CL. That web app that serves another kind of customers than the Django one? A CL app. It isn't for everybody and sometimes you wish for a bigger ecosystem, but anyways, many things can be done in CL.
[1]: ok honestly the learning process was not that easy. It's a bit easier with my contributions now! (Cookbook etc)
To me, the way to think about "introducing" someone to Lisp and helping them understand the choices the language makes - you really want to focus on the things that make Lisp stand out, and one way of doing that is re-using a lot of the functionality of a language that outsiders may be familiar with.
I generally agree that if you want to build something substantial you probably would choose CL over Hy, but if you want to "try a lisp" while minimizing startup costs and you have a Python background I think Hy makes way more sense.
If you choose language X, I advise learning it alongside people who know and use X. It seems like a no-brainer to me, and not all that expensive.
Basically, that "if you choose..." advice is for the wrong context, specifically because of the "if you choose" qualifier. I'm talking about what happens during the pre-choice time period.
While I won't claim nobody has been "psyched out" by a community of people for their language-learning choices, I just don't think that this is something that happens with Lisp particularly often. The subreddit r/Lisp is predominantly Common Lisp, but more frequently than not accepts viewpoints from Clojure, Scheme, Racket, and even NewLisp programmers all in the same comment section, without bickering.
As I said, if anybody wants to learn a new technology, and that somebody is a social person, finding a supportive community is important. If somebody going to be a novice at a particular language, and broadcast opinions about that language on disparate forums—some opinions which may come across as uninformed—sure, people will likely come out of the woodwork to correct them, suggest allegedly superior technology, etc.
I will give you this, though, you can have a taste macros with Hy, which of course are one of the cornerstones of different Lisps.
With that said, I do not feel like one would accurately develop a feel for what Lisp is like (be it Common Lisp, Scheme, Racket, or Clojure) by using Hy.
Very much agree! I shouldn't have framed it that way.
I should have said: each time you make a hybrid language between approaches, there are new people who will try it out and may discover a new and exciting way to program. Some people, in hindsight, will certainly wish they had jumped further - but they can always do that in the future and some will only make the bigger jump after making a smaller one.
As such, for Hy’s target audience, I think it’s not as much “ditch a new language” as it is “I have to use Python for $reason, but I want something more like CL”. A good example of this would be any machine learning related work.
Last but not least, I would expect that on HN we focus a bit more on “what could make Hy even better” rather than “what other languages are better than Hy”.
For a type-racket equivalent, we now have Coalton, it's like a Haskell on top of CL: https://github.com/coalton-lang/coalton/ Its author says:
--
Take this with a grain of salt, because I’m neither a user nor expert of Typed Racket, but:
All in all, I think the biggest and most important take-away is that Typed Racket goes through great effort to seamlessly blend with ordinary Racket. But that means Typed Racket has to compromise on type system features that can only be supported if you’re willing to change the language itself.Coalton puts the type system first, opting for something close to Haskell, at the expense of not being a system for gradually typing Common Lisp, and instead being a separate language altogether.
---
They use it for their open-source quantum compiler and other things.
In truth, I've never understood the fuss about `let`. It's taken a huge amount of Hy development effort, but it adds very little. Python's native scoping works well in the large majority of cases, and in tricky cases, I'd rather use `nonlocal` or `global` explicitly so I'm sure what's happening at the level of Python semantics. I suspect that people who heard that previous versions of Hy were missing `let` assumed that some other feature (such as nested lexical scopes) was also missing that has actually always been there.
Python is generally quite good at having a verbose option to express things, so I'm surprised there's not an explicit way to reference the enclosing scope from an object.
"a = a" just means assign the value of local variable "a" to a local variable "a", so the error makes sense.
But maybe it is because I am familiar with Clojure (where `setv` or equivalents doesn't exist) and not other Lisps.
This would be the equivalent Clojure code anyway:
So yeah, makes sense for me. But can understand why some other people would expect the other way though.My use case is Jupyter notebooks. With normal Python scoping you quickly accumulate a lot of global variables and you might accidentally use the wrong one. With let-bindings you have variables that clean up after themselves.
On the other side I am quite torn about the decision. I quite liked the early releases of Hy where I could write a code that was much more familiar for me as a Clojure programmer. Still need to give a try to the new releases though.
Ctrl+F and try skipping to ";;;; The Basic Macros" section on that quick start page. Is that about the right level?
For many of the "popular" languages, a simple cheat sheet showing the equivalent control structures in a common language might be enough for a typical programmer to get a handle on it quickly. But Lisp is not an Algol-family language. It's different enough that such a document would fail to convey what makes it interesting. There's an inferential gap that must be bridged first, and it's difficult to do that quickly; a programmer mistakenly [expecting a short inferential distance][1] may not have the patience to "get it".
[1]: https://www.lesswrong.com/posts/HLqWn5LASfhhArZ7w/expecting-...
1) Simple, one way to do everything. for loops, code formatting, and project setup.
2) Good concurrency (go routines)
3) No exceptions, readable code and error handling.
This article is titled "Why Hy." It should sell me on the language with a philosophy supported by examples, not instruct me on how to write a floating point number (unless the language has a interesting take on writing floating point numbers!)
If you desire a path closer to clojure
Basilisp is much less mature than Hy but tries to stay generally compatible with Clojure, which is somewhat different than the goals of the Hy project.
For my part, I had similar "this doesn't feel quite like lisp" moments with both Hy and Clojure. And I ultimately realized that it's the mere presence of the base platform that gets me. With Racket, I'm used to feeling like I'm sealed off in my own lispy bubble. With Hy and Clojure, there are always bits of Python or Java (read: Not Lisp) hiding in the background. Many differences from other well-known lisps that I know are attributable to that base platform.
And that's a catch-22 situation. Objectively, they might be (and, IMO, are) good design decisions. But they're also covered in non-lisp cooties. I don't know why I should expect otherwise. Brains are weird.
I think in isolation it would be a fairly bad choice, but in practice you get a drop-in replacement for lua that fixes every problem with lua.
I wouldn't want any other lisp to work like it does but damn I consider that thing my savior at this point. I've come to really hate lua and fennel bails me out every time.
I wonder: 1/ Shall we mix multiple paradigms of programming in the same project? 2/ Should we adopt a single programming language that supports multiple programming paradigms and utilise single paradigm for each given project, for the sake of minimizing the skill set required to the development team and – not necessarily –the learning curve?
https://github.com/natrys/ghdl
I definitely had fun writing that in Emacs/hy-mode. And indeed having access to Python ecosystem is neat. However, if I may:
- I didn't get to use any intellisense, which was quite painful. Is there any Language Server for Hy now?
- I think Hy tends to break with every Python minor release, which is a bit annoying. Stable is still broken on 3.10, and alpha is a big change.
- This one might be a matter of subjectivity but I felt that Hy is trying to be more "pythonic" and less "lispy", and I am not sure what to feel about that. For example, familiar things like `&kwargs` or `&optional` seems to have got replaced with something less familiar (particularly, change to `#**` for keyword arguments spurred this though).
2. That's right. Hy depends much more on finicky details of Python than most Python packages do, particularly Python's AST, so each 3.x release of Python usually requires some changes to Hy to support it. Each Hy release supports (at least) all versions of Python 3.x that have been fully released and are not yet at end of life.
3. More precisely, we're trying to take more of our conventions from Python rather than Clojure. See https://news.ycombinator.com/item?id=31250992
Its read language (Lissp) doesn't have an Emacs mode (yet), but its syntax is so close to traditional Lisps that lisp-mode pretty much works. No completions either. There is Lily https://github.com/mjobuda/damascus-tools/tree/main/lily, which proved Jedi works on Lissp, but it's never quite worked right.
Hissp leans more Lispy than Pythonic compared to Hy, but still prioritizes interop, so the object model is still Python's.
One of the reason as i know is, they have no idea of using unit test.
I think metaprogramming is a bit of an antipattern in most use cases(The more power the programmer has, the less the compiler/linter can follow it, just because... there's more possibilities).
But for the stuff it that needs it, it seems to be unbeatable, and this seems to have type annotations, which fixes one of traditional lisp's ickiest things.
Plus, having access to Python's libraries should make it possible to really evaluate what LISP can do by as a language, rather than having the choice mostly made by ecosystem size for a lot of projects.
I prefer a much more predefined structure that fits in better with autocomplete, and focuses more on reuse than new ideas, but I could see some pretty amazing stuff being done in this, and it being a benefit to the Python community as well.
You don't really have any help from the system, it's all on you to ve sure you've tested every code path, and it doesn't produce any formal report like a linter's "No warnings" message.
Nor can other devs repeatably do the same tests and get a list of warnings to work on, you can't automatically test the whole existing codebase when new static checking comes by, etc.
It's super useful for experimenting when you aren't sure of something, and it's great to have a debug console in the debugger, but I've never been that much of a fan of interactive programming in most cases.
Plus, the same annotations used for static analysis also drive modern intellisense type tools, so even the most quick casual project can still have some pretty great documentation with almost no time cost.
(/ (+ (- b) (sqrt (- (* b b) (* 4 (* a c)))) (* 2 a))
is a pain in the neck, and even more so when you have to read it, while
(-b + sqrt(b*b - 4*a*c))/(2*a),
can be checked on the fly.
I am kind of amazed this hasn't finally sunk into programmers' hearts.
I can write ugly code in any language. But if I was writing this code in Clojure, I would probably do something like this:
That is perfectly readable to me.Yeah, you may say that I needed to introduce other variables to make this work and the code got bigger as a result, but on the other side how many people are actually writing this kinda of code on daily basis that are not Computer Scientists?
Sure for the four-function calculator operations, operator precedence levels are a reasonable notation, but when you can define your own functions, and your programming language now needs a dozen precedence levels, it becomes hard to read. Lisp is completely unambiguous.
[1]: https://github.com/gilch/hissp
[2]: https://hissp.readthedocs.io/en/v0.3.0/tutorial.html#hello-w...
[3]: https://hissp.readthedocs.io/en/v0.3.0/tutorial.html
[4]: https://github.com/gilch/hebigo#bracketed-expressions