Hacker News new | past | comments | ask | show | jobs | submit login
Why Ruby is an acceptable Lisp (2005) (randomhacks.net)
139 points by behnamoh on Sept 10, 2016 | hide | past | web | favorite | 113 comments



Author here. Let me explain where this article came from, since people keep digging it up every few years to discuss it. :-)

I spent some time during the late 90s learning all kinds of cool ways to use Lisp, courtesy of a couple Boston startups, including IS Robotics (now better known as iRobot). And I'd helped introduce Scheme to another employer. Scheme made it ridiculously easy to create domain-specific languages, but it was obviously a niche technology.

Around the same time, I'd heard Matz speak to a room full of Lisp hackers at the Lightweight Languages conference. He'd elegantly explained why Ruby was cool several years before Rails became widespread. But I don't think many of us understood what he was saying until later.

When I finally realized—thanks to Rails—that Ruby could mimic many of the most common Lisp metaprogramming tricks, I mentally kicked myself and wrote this blog post. There were a lot of great responses from other bloggers, too. My goal was to help make other people aware of some fun new possibilities.

Anyway, if anybody has any questions, I'll try to answer them later this evening when I'm back online. It's fun to remember when this stuff was so new.


Interesting article and thanks for chiming back in. I also discovered and enjoyed the metaprogramming features of ruby perhaps 5+ years ago. As I've been learning it recently, can I ask if you have any particular take on lua? To me, it feels a bit paired back, more purist, smaller core/libraries than ruby, but also drawing from the same tradition - ie. lua's tables/functions equate well to lisp's lists/S-expressions (though practically largely equivalent and including metaprogramming support) - despite not being directly derived from a lisp.


Lua looks like a nice language, but I've never really tried it. The Lua JITs are really impressive.

Metaprogramming is a pretty widespread feature these days. Even Rust, for example, has both Scheme-style rewrite-rule macros, and (unstable) programmatic macros, for example.


OFF-TOPIC: Why Lisp was so popular in Boston ?


MIT is in Cambridge


Good point...so students learnt it in college and then worked in boston/cambridge


For a 2005 article, this is totally awesome, as well as that Steve Yegge response. But in 2016, it's hard for me not to get excited about what's seeming like Clojure's upward trajectory into the mainstream and want to jump on board 100%. To paraphrase Rich Hickey, Lisp and immutable functional programming totally rock, and the JVM and Javascript just reach (ClojureScript). As our systems get bigger and more quality is demanded from our software, it'd be so cool if we could get behind this as a standard, in all the growing technology fields -- web and mobile (Om.next for React and React Native), big data, AI/machine learning, Dockerization/containerization and even decentralized stuff (Pelle Braendgaard's Cloth library for Ethereum is a start, and I was just talking with some awesome people in the Clojure Slack channel working on Boot tasks for IPFS, and soon the Golem Network project will be launched which will be super dope). Hell, even designers can start designing in Clojure now. It's just Simple, and as the community grows, rallying behind it is only going to get Easier. People were raving with React Native about how JavaScript developers could now all the sudden start coding mobile apps. How cool would it be if there was just one awesome language, the one true immutable functional Lisp, where everybody in tech could understand each other and collaborate? I genuinely think this is Lisp's revenge (shoutout David Nolen!).

It's been tough for me as a new developer to learn this stuff without nearly as many resources as there are in the Python and Ruby communities for beginners, but there's no question this is the (near-term) future. Plus, a lot of that stuff just complects everything anyways and doesn't have to apply anymore (read: mutable state, no Value on Values).

Let's keep it Lispy. ;)


...No. Clojure is lisp, but it's a weird little language, and most of us lispers schemers aren't really totally happy with it. We're used to more flexability, less functionalism, and less opinionation. Plus, Clojure's equality and conses don't work at all the way a lisper would expect, and the conses are way less useful.

There's a reason there will never be a universal language.


As a clojure fan, I agree about conses. It's rare to use something like that.

Personally I'm happy equality doesn't work like common lisp. See here for a post explaining equality in CL: http://eli.thegreenplace.net/2004/08/08/equality-in-lisp

It's old and I don't use common lisp, so please correct me or the post if needed.

I've tried to get into lisp numerous times. Clojure was the one that clicked for me. I can't say if that's because of clojure or because it was just the right time for me after N times trying to get lisp.

Every now and then I poke at a scheme or CL. Scheme seems do-able for me, particularly Chicken Scheme, but I everytime I try CL, I come across what seems like too many functions to do what it seems like one function should do. Whenever I see something like SETQ, etc., that's a big turn off for me.

On the other hand, I really dislike Clojure startup times. Doesn't matter for server/long running apps, but when you are debugging or iterating, it gets to be a pain. There are some mitigations with their own set of issues.

Pixie and Hy have been brought to my attention. Pixie seems more or less abandoned, but Hy looks interesting. I haven't been able to tell from the docs if it has clojure datastructures (which are the best part of clojure), but Pixie does. So maybe sometime I'll try to bring those over to Hy and see if they get accepted.


No, what I mean is that clojure's conses are fairly useless compared to the conses of CL and Scheme.

Also, all programming languages implicitly have most of those equality operators. But yeah, Scheme's equality is a bit cleaner.

Not a super fan of Clojure datastructures. If I want to modify my cdr, than let my modify my cdr.


How are conses in Clojure different from those in Scheme? Is it the implementation? Because the API seems the exact same to me.


Not a Clojure developer but, IIRC, Clojure doesn't have conses, but rather the function cons creates a seq. A Seq in Clojure is immutable and (usually) lazy, which is nothing like what CONS makes in lisp (a mutable pair that is the basis for building singly-linked lists).

Note that linked-lists are only a good data structure in fairly narrow contexts (e.g. shared structure, certain types of mutation you can do). Since Clojure is focused on limiting mutation, it makes sense for them to use Seqs instead of linked-lists.


Didn't racket also make the decision to make cons create immutable lists (and using mcons for mutable lists)?


But that's not what Clojure did. In clojure, cons cells aren't cons cells: they're a cell that can point to a sequence of some type, and contain a pointer to arbitrary data. They also do not make up lists, and have weird equality rules.

Racket's conses are merely immutable.


However, conses can be used for other types of structures as well.


Yes, CL has old and useless functions.

There is zero need to ever use SETQ for example. SETF is a pure superset of SETQ's functionality. For other examples, I don't know that I've seen a serious use of PROGV or PROG in this century either.

Most experienced lisp programmers never use EQ, since EQL is more well designed, and the overhead is either negligible or nonexistent. Yes it's annoying that EQUAL doesn't recurse into non-string vectors.

Useful equality in CL is roughly: EQL for comparing non-aggregate types and identity checking of aggregate types, EQUAL for comparing lists, and EQUALP for case-insensitive comparisons. Some people add in = for numeric comparisons, but that's a style question (seeing = means "these arguments are definitely going to be numbers" and not much else, as if you're doing an equality test between e.g. an integer and a float, then you're doing it wrong, and differing numeric types is the only time = is different from eql when all arguments are numbers).

Comparing non-primitives is typically done with domain-specific comparison functions, as there are many ways to decide if two arbitrary objects are "equal" (for the old-saw OO example of 2d geometric shapes, you might have shape-area-equal, shape-perimeter-equal ...).

Some things I like about CL:

I find its package and macro system combine to be an amazing sweet spot of simplicity and power for metaprogramming. Scheme's hygienic macro system is the stuff that earned several people their doctorates; CL's is explainable in under a minute, and 99% of hygiene problems are solved by the package system (IIRC Clojure inherits its macro system from CL, using namespaces to solve the "FLET" problem).

CLOS+MOP made me feel like perhaps OO wasn't the colossal mistake I'd always thought it was. It's not the only great OO system out there, but it is great and so much better than the various C++ derived systems that I was first exposed to that it will always have a special place in my heart.

The condition system is great. The only quick summary I can give is that you can have condition handlers run in the stack context in which the exception was thrown (which lets you do much more useful things than running in the context in which you establish the handler, which is how all other exception systems I've seen work).

Common Lisp has amazing development tools; in particular the combination of SBCL/SLIME puts nearly all other F/LOSS IDEs to shame[1]:

It's a high-level language, but you can view a function's disassembly, capture instruction-level profiling data, recompile a single function and rerun all of that without stopping your program. The overhead of edit/compile/debug is essentially zero.

All that being said, I do need to take a look at clojure again (I went through Fogus's "Joy of Clojure" book when it was new, but haven't touched the language since).

1: http://pryrepl.org/ is apparently an attempt to bring some of this to Ruby, but I have on many occasions seen Ruby programmers say that "If you're using a debugger you're doing it wrong" and Pry doesn't seem to have a lot of users.


SETQ has to exist, because it is the primitive SETF is based upon. EQ is tremendously useful for testing identity (which is kind of its job...), and conveys intent (although I mostly use Scheme, where eq? is a lot nicer). And Scheme's macros don't have to be super complicated. explicit rename, implicit rename, and syntactic closure macro systems are almost as simple as CL macros.


On the other functional JavaScript side of things, there are also Elm and Purescript. They are both pure functional, and both are ML-inspired (strongly typed, equational, ADTs with pattern matching, etc.). Elm is a fair bit easier to get started with, but has a substantially weaker type system and fewer useful functional features. Purescript is a fair bit more difficult to get started with, but has a substantially more powerful type system and lots of useful features like effect types.

Personally, I greatly prefer ML style languages to lisp style languages. I get fed up with all the parentheses in lisps.


BuckleScript is worth a look too:

http://bloomberg.github.io/bucklescript/

It's in the OCaml-to-JS family.



Yegge's predictions don't always come through, but he basically predicted Clojure. (The "someone will Thorvalds Arc" bit.)


Thanks for this, I nearly fell of from the couch reading it. Yegge's writings are extremely entertaining.


> The most common use of LISP macros is to avoid typing lambda quite so much

LispWorks

    CL-USER 1 > (let ((n-macros 0) (n-with-macros 0) (with-macros nil))
                  (do-all-symbols (s (values n-macros n-with-macros))
                    (when (macro-function s)
                      (incf n-macros)
                      (let ((name (symbol-name s)))
                        (when (and name
                                   (> (length name) 4)
                                   (string-equal name "WITH" :start1 0 :end1 4))
                          (incf n-with-macros)
                          (push s with-macros))))))
    1186  ; macros
    182   ; with- macros

Note also that

    (with-slots (a b)
        foo
      (+ a b))

is not the same as:

    (call-with-slots foo
                     '(a b)
                     (lambda (a b) (+ a b)))


Tcl is a better lisp than Ruby. Homoiconic. Yield and tailcall make it easy to implement FP idioms. Macros available if needed.


I am forced to use tcl a lot at work because that's what the ASIC tool vendors settled on for a scripting language. It's awful. Just plain awful. I avoid it as much as I can, but I'm forced to use it thanks to Synopsys.


The Synopsys extensions are a bear to work with but at least you can define procs which was impossible to do with their old scripting language where you had to simulate them with repeated includes.


...TCL doesn't have macros, it has fexprs, and uplevel. It also has no effective hygene mechanisms, and unless you want to use regex to parse the tree every time your function fires, you can't do the kinds of code transforms macros can.

Don't get me wrong, TCL is super cool, but it doesn't have quite the same powers as lisp. Although you are correct, it's a better lisp than ruby.


Do you know about the sugar package? Sugar implements real lisp macros.

http://wiki.tcl.tk/11155


Huh. Meh, the lack of Macros aren't a problem. The problem is lack of arbitrary code transformation, which isn't as fixable.


There is arbitrary code transformation in tcl through subst or string map


Yeah, because regex is so great at parsing code. It's not like that'll break in unexpected ways. And we all just love writing parsers in our macros.

Oh, wait.


Wish more people understood this about tcl


Maybe this is meta (the wish) :-)

I remember tcl in the early 90s then it disappeared from my radar, mostly web and mobile apps. Honest question: is it still relevant in some application area?


It's still great at what it was always great at: embedding, acting as a CLI, and doing random grunt work. It's Lua's main competitor, but it can also go toe-to-toe with python and ruby in terms of libraries in some areas (although by no means all of them). In particular, the ease of construction for graphical applications is arguably unmatched (IUP is probably the closest), SQLite was built to work with it, metakit is handy as well in the DB area, it has a great event loop, several OO models, solid unix integration, sold FFI and excellent embedding (as you'd expect), Starkits, which means you can distribute your whole TCL sourcetree as a single file, and a ton of other stuff.

So while it's not as popular, relevancy isn't an issue.


This comment is a great representation of tcl's relevance, which is nice to see because tcl is often dismissed nowadays by people who don't understand it


Hah yeah I realized that meta joke after I posted


Tcl was what made possible one of the best startups I had the privilege to be part of.

They had a Rails like stack in the first .com wave, but implemented in a mix of Tcl, Apache and C.


Can I get your email? Would love to hear about that stack


It had an architecture similar to AOL Server, just that the founders did their own thing instead.

Tcl was used for almost everything, C for performance critical modules and DB FFI.

The interpreter was loaded as an Apache module.

We supported Informix, MS SQL Server, Sybase SQL Server and Oracle.

All major UNIX variants from late 99's and Windows NT/2000.


1) "Ruby is a denser functional language than Lisp"

Lisp is a regular, explicit language. It does not strive for micro-density, but rather tends to achieve macro-density by allowing more expressive abstraction and transformation in the larger componentry of your projects. Its regularity around parenthetical s-expressions is the source of its expressive power.

Things like "(+ (aref x 3) 4)" are larger than "x[3]+4", but the operations and source code representation in Lisp are directly accessible to matching and transformation without having to go through extra rigmarole to deal with classes of AST node objects or whatever. This makes the usefulness of these features immediately accessible for everybody, and useful for quick utility, not just major undertakings.

2) "Ruby gives you about 80% of what you want from macros"

I'm sorry, but this section is simply ignorant of what Lisp macros are for. Simple syntactic sugar is obviously possible with macros, but certainly not its major use. Providing abstract constructs that require custom code generation, creating lower-level boilerplate (like auto-creation of introspective data structures and customized support functions), and wrapping custom behavior around inline scopes are the primary uses I've seen and used. Ruby can do a little of it, but I see no "80% of what you want" there.

3) "Ruby's libraries, community, and momentum are good"

Quicklisp was created after this article was written, and has been a godsend to the community. Lisp tends to develop a very roll-your-own culture because of its quick accessibility of creating abstractions, but the ease at which you can publish & reach for libraries with QL has brought a lot of cultural shift towards reuse. There are a ton of both esoteric and practical libraries out there, and common libraries for threading, networking, OS interaction, etc, are the common case now (and actually have been for quite a while before Quicklisp as well).

There's tons of interoperating flexibility because the language provides a huge baseline (especially its object system), so it doesn't all need to lock into an on-top-of-Lisp infrastructure that would constrain the programmer's options. It's a good time to be in Common Lisp.


I have read the point about hygienic macros before. I have been working with racket and common lisp and I cannot tell you have annoying it is to be trying to learn macros and continually having to fight racket to let me do what I want. CL on the other hand seems to get out of the way and let me shoot myself in the foot just fine. I'd say that from a learning standpoint hygienic macros (ala define-syntax) are a roadblock because they introduce a whole bunch of hidden state which is hard to build a mental model of until after you have encountered regular macros.



I just checked your links and gotta admit, you're right. Although, still I don't see why this has been posted "three" times before!

I think HN should add a URL checker in the submit page.


I don't see why this has been posted "three" times before

Posts that are over a year old can be reposted. It's a way to get a fresh perspective on ideas.


Maybe an URL checker would implicitly discourage reposts by warning about them.

If the URL checker were to present itself as a helpful aid for reposts ("You might want to use this title like the other 3 posters have used: X") it might avoid that result.


I missed the original postings so it's nice to see this for the first time. That would be difficult if HN didn't allow reposts. Though I can understand if I had seen it already.


That's actually a good point.

On a sidenote, I get a weird feeling from HN's simple UI/UX. On one hand, it feels like the old CLI machines that put the focus on "content" - kinda like "do one thing and do it right".

On the other hand, reading all the comments, getting notified for replies on comments, etc. is really hard. In this day and age, one would wonder if HN really hates JS (well, except for the upvote button).


Community building and maintanence is a very tough job, and the simplest of things can end up nudging people towards unproductive behaviour. HN has consistently maintained at least a decent quality audience and community by keeping the UI free of bells and whistles, and thusly being unattractive to certain kinds of low-effort users. At this point, any change would probably count as messing with a good thing, so the thinking is probably "if it ain't broke, don't fix it."

They are however acknowledging some things that are broken and need fixing, and in recent days have been introducing small changes to correct those (eg. the unvote button, ability to fold). I do hear you on the notification for comment replies thing though, I was really surprised when I found that feature wasn't there, and at times end up missing out potentially good conversations because I didn't check for and notice the reply comment until too late.


For getting notified of replies, try http://www.hnreplies.com/.


Thanks, will give it a shot.


I agree with you that HN has maintained a quality audience and community. This is really one of the main reasons I check HN daily.

Although, even top-tier programmers and professionals are eventually "human", and design rules apply to them as well. It's like saying these folks are more comfortable to use some strange user-undfiendly smartphone, while they mostly just use an iPhone instead.


I think they've demonstrated they know what they're doing. Perhaps it's us who should learn from them.


I get some weird enjoyment out of this site not having any modern features and little to no JavaScript, even though I'm a JavaScript developer by trade and open source.


There is no need for JS on a site like this. The ajax upvote is convenient but they could (unconveniently) reload the whole page and set the location with an anchor to go back to the upvoted comment, 1994 style. Any real time notification system requires a backend infrastructure that maybe they don't want to have.

They could also use a better CSS but who cares, text is king in this kind of sites. It displays quite well on mobile too, especially with Opera which reflows text after zoom (the very reason for using Opera).


I find notifications on replies annoying. The one feature I do want is better understanding of where upvotes are coming from: when I suddenly get a surge of upvotes that didn't seem to come from any comment of late, I just kind of want to know where it's coming from, as it annoys me to no end. I wish I could somehow see which of my comments were upvoted recently.


"It seems that perfection is attained not when there is nothing more to add, but when there is nothing more to remove."


I don't think this article is as great as a lot of people do. Most of this should be obvious to anyone using ruby, nowadays. It might have been great at the time, but I don't think so much now.

But then again I thought "The Meme Hustler" was rubbish (well, actually, I think the reasoning would have been okay, but I thought the premise was totally, utterly, removed from reality), so I think it's safe to say me and Steve have a different taste in articles.


I don't actually know how I feel about the contents of this article, but it _did_ generate a lot of lively discussion and made a lot of people think. So in that regard, I feel that it's a success.


Fair 'nuff. That is a major benefit. Steve Yegge's response is way more interesting than the article itself.


>But, for the sake of argument, I'd like to boil them down to two things:

> LISP is a dense functional language. > LISP has programmatic macros.

First of these is wrong and second is just picking something from feature list without any concern of practicality.

When someone writes Lisp as LISP and speaks about functional programming, I get the feeling that the author has not programmed with Lisp outside school (or knows only Scheme). Using toy examples don't help.

There is also problem with treating Lisp as a single entity. It's like Treating C++ and JavaScript as the same language because their syntax looks similar to someone who is not actually programming with them.

There are at least two major "schools" of lisp. Traditional Lisp style with Common Lisp as it's main representative, another being Emacs Lisp. The another is Scheme & Clojure camp that goes towards different goals.


> When someone writes Lisp as LISP and speaks about functional programming, I get the feeling that the author has not programmed with Lisp outside school (or knows only Scheme)

I've actually spent something like a dozen years writing Common Lisp and Scheme macros for production software. But I try not to put the nastiest ones in blog posts. :-)

In my experience, real-world Lisp was often a surprisingly functional language, certainly by the standards of the day.

Based on working with real-life Lisp code bases at four different organizations, I also stand by my description of what Lisp macros in production software typically looked like in 2005. Sure, there was the occasional elaborate macro that implemented something like Prolog in Lisp, and it was glorious. But the actual day-to-day workhorse Lisp macros were less flashy—most of them delayed evaluation, improved syntax, or performed simple transformations.

Many of these things are extremely valuable, but you don't necessarily need the full power of macros to implement them.


Like I said: the featured I find myself giving thanks for the most in lisp is 1) never having to remember syntax, or remember exactly how it works in ambiguous contexts, and 2) never having to figure out how to indent my code.

The big stuff is there when it matters, but the little stuff makes up the day-to-day.


I agree regarding this mischaracterization of Lisp, but have to laugh as you mischaracterize Scheme. Lisps are excellent at adapting to programming paradigms. Scheme is often used academically in a functional style, but if you want to go full-on procedural or OO, we're just as good as Common Lisp. Okay, maybe we're not quite as good as CLOS, but we're not as far off as you'd think.

Also, most Schemes are way more traditional than Clojure. And no, Racket isn't Scheme.


I had to simplify in the sake of argument.

Both Common Lisp and Scheme are multi paradigm languages by nature, but Scheme took philosophical step into different direction. This philosophical difference may follow into other Lisp dialects that are not exactly Scheme.


...I would say clojure goes in a third direction, but fair enough.


I'm glad to see this, and even more interested that it was written in 2005. I've read lots of rants about how awesome Lisp is supposed to be from Paul Graham and Steve Yegge and all. But I never see anything significant being written in it or using it, so I haven't felt all that tempted to try learning it.

Meanwhile, I've been using Ruby for a while, and the metaprogramming capabilities are pretty cool. It sounds a lot like what all of the Lisp enthusiasts are saying is so awesome about Lisp to me. So I've always wondered what it is that's so awesome about Lisp that Ruby doesn't already have.


I couldn't have written a unit test framework which evaluates at compile-time in Ruby, but I did in Gambit Scheme. (If any test fails, the compiler exits in error, and no library or executable is produced.)

https://github.com/billsix/bug


Macros, arbitrary syntax transformation in macros, and object system as powerful as CLOS, and the ability to comfortably use almost any paradigm.

It's not always useful, but you'll be thankful when it is.


> Macros

I don't want my teammates writing macros. When the whole programming community constantly talks about situations within regular, more expressive languages (with syntax) like Python et al, where teammates look at each others code and are baffled ... I struggle to see a scenario where inventing syntax through macros is a good idea, and won't make that situation so much worse.

> arbitrary syntax transformation in macros

See above.

> object system as powerful as CLOS

If I wanted object systems, there are plenty of options that don't involve so many parens, and do involve far more expressive syntax than lisps offer.

> and the ability to comfortably use almost any paradigm

I kinda want my team to pick a set of known paradigms. This is not an advantage. I've never used a modern programming language and thought 'this lacks paradigms, if only I could let my team invent some, that none of us will understand 6 months from now'.

> It's not always useful, but you'll be thankful when it is.

From what you've said, I won't. I'll just be upset my teammates invented their own language with macros and whatever paradigms they chose to use that day.

The lisp 'trade-off' (homoiconicity over expressive syntax) just seems like it's looking for power in the wrong places. Power like that is more often bad than good.


On large team Lisp projects, you don't have every individual person arbitrarily invent their own idiosyncratic abstractions. The project as a whole has a common set of abstractions that fit the domain, which are designed and modified the way you would design the abstractions regardless of the language and programming paradigm you're using. Macros are a language tool that lets you build abstractions, in some cases imo allowing better abstractions than would be possible without macros, most often ones simulated somewhat clunkily with OO features (or something godawful like C++ template metaprogramming). But if you have a large team project you still need an actually functioning team, common abstractions, project management, documentation, code review, the usual stuff. Successful large Lisp projects of course do all that.

I don't see a meaningful difference here between Lisp and, say, C++. If you have a dysfunctional team writing C++, full of cowboy programmers who invent their own idiosyncratic abstractions without coordinating with anyone, the fact that C++ doesn't have proper macros doesn't really meaningfully limit the resulting damage. What stops it from happening in functional teams isn't that C++ the language prevents it from happening, but that the team has some kind of working process that allows them to successfully write code as a team.


> Macros are a language tool that lets you build abstractions, in some cases imo allowing better abstractions than would be possible without macros

I guess 'better' is the operative word I question here. You're creating syntax that no-one has seen before. How is that a good thing? Meanwhile in languages like Python, with syntax built-in, people build large apps without ever doing this, making their code far more understandable for the next person that touches it.

> I don't see a meaningful difference here between Lisp and, say, C++.

Agree :) (joke)

> If you have a dysfunctional team writing C++, full of cowboy programmers

At no point am I suggesting we can save ourselves from cowboys ...

> What stops it from happening in functional teams isn't that [...] the language prevents it from happening, but that the team has some kind of working process that allows them to successfully write code as a team.

But people are arguing 'the whole point' of lisps is that you can be free to do this, and that this justifies the 'paren-crazy', syntax-less language trade-off, and I'm yet to see any justification it's necessary to allow this at all.

What I'm saying is that all the lispy parens give you are footguns and all you're saying is that good teams don't use those guns. Well then why give them the guns at all. Why not give them a language with decent syntax instead.


> in languages like Python, with syntax built-in, people build large apps without ever doing this, making their code far more understandable for the next person that touches it.

No. Lack of macros makes code bigger, more error prone and harder to understand.

When I use something like cl-ppcre:do-register-groups, this one word hides all the loop orginization, and underlines meaning of local variables which are bound to regex groups.

Or cl-sql:select, which allows programmer to use SQL syntax as part of lisp while hiding special chars escaping, converting numbers to strings, ensuring sql-syntax is correct.

Or simplier example: gl:with-primitives, which hides glBegin(...); ... glEnd(). Note, it not just hides, it makes impossible to forget glEnd.

Macros makes life easier. Otherwise no one would use it. Macros such a convinient way to make an abstractions, that switching from Lisp to any other language becomes a great pain. One start to see tons of boilerplate code, which needs to be written by hands, checked for errors with eyes, which hides logic somewhere inside of boilerplate... It becomes better with practice, but nevertheless lisp nostalgia will last forever.


> No. Lack of macros makes code bigger, more error prone and harder to understand.

I mean, that's provably not true. Or at least not provably true. Let's me throw out at alternative statement: Macros make code harder to understand.

> When I use something like cl-ppcre:do-register-groups, this one word hides all the loop orginization

You don't need a lisp to hide things.

> Or cl-sql:select, which allows programmer to use SQL syntax as part of lisp

Eugh, why would I want SQL interspersed with my lisp.

> [...] gl:with-primitives, which hides glBegin(...); [...] Note, it not just hides, it makes impossible to forget glEnd.

Python has 'with' statements and 'context managers' that do this (make sure some cleanup happens). [1]

> Macros makes life easier [...] such a convinient way to make an abstractions

They might make life easier for the person writing them in the short term. And I can see how when you find a really solid case for a macro and document it well, open source it etc, it can be broadly useful, but then why not add that case to the damn language. Instead you open the floodgates to everyone in your team writing macros that aren't widely understood.

> Otherwise no one would use it.

A lot of people avoid them like the plague in all other languages.

> but nevertheless lisp nostalgia will last forever.

Maybe that's all it is though.

[1] https://docs.python.org/2/reference/compound_stmts.html#the-...


So python's got a with statement. What of it didn't? Well then, you're screwed. Hope you like calling file.close().

If lisp didn't have a with statement, you could add one yourself. And if scheme didn't have a with statement... well, you could always add one, but before dynamic-wind got added, it could break if you weren't careful. Some things you can't just patch in. Anyways, now you'll say, "but python does have a with statement!" But that was just an example: there are things python will never have that you can add to lisp yourself. Like multimethod dispatch and generics, anaphoric operators, lazy module loading, pattern matching, ADTs, regex literals (with readtables), non-deterministic operators (the classic amb/assert operators, for concisely expressing searching a list (although they're admittedly more toys than anything else)), and a full-on looping construct that allows for awk-style record processing of arbitrarily typed records from arbitrary sources (field splitting is handled by other functions). And that's just a few examples I came across. There are dozens of others.

That would never all be added to the language: there's way too much, and it's too specialized.

As for your theory that macros make code harder to understand, you don't have any proof. In fact, you have negative proof, as there's plenty of logic, good programming practice, and individual cases indicating that careful use of macros can make code easier to understand.


> But that was just an example: there are things python will never have that you can add to lisp yourself.

I don't want to. I want a language that has sensible norms.

> As for your theory that macros make code harder to understand, you don't have any proof. In fact, you have negative proof,

Do I?

> as there's plenty of logic,

What logic? Show me. I simply don't believe you, as it's not provable.

> good programming practice

Still not proof. I believe the opposite, that it's not good programming practice to use macros.

> and individual cases indicating that careful use of macros can make code easier to understand.

So anecdotal evidence, not proof.

You have no more proof than I have. My personal experience is that in most mainstream languages, the most common arguments are over invention of frameworks/libs vs using existing ones, and those devs certainly would not be happy having macros thrown in the mix.


Lisp has sensible norms: Unlike you, however, we acknowledge that what may be a sensible norm for one situation isn't for another.

As for it not being good programming practice to use macros, it isn't good practice to use them excessively. However, they can be used to make your code much clearer. Here's the reasoning (no, it's not proof, you're right on that front, but it's better than what you've got, which is just your opinion, AFAICT):

The larger a codebase grows, the more likely it becomes to contain bugs. The most common way to decrease the size of the codebase is to abstract away common idioms, only express them in one place, and reference that from other parts of your program. This is what functions and objects do. However, some idioms are incapable of being expressed as functions or objects. These idioms must be repeated over and over again, and often result in insidious bugs when somebody gets them subtly wrong. Macros allow you to capture those idioms and express them once, eliminating bugs. They also make the code easier to understand, by abstracting away complex logic.

So, if you really think macros are bad, than what's wrong with that line of thinking?


> Lisp has sensible norms

Again, in your opinion. And sorry to start blunt because I'm trying to round this out as we're getting nowhere :)

To me, writing my code like it's an AST, amid a ton of parens is not sensible. I've happily not done so in many other languages for over a decade, and I'm very happy with that state of affairs (or at least I'm relatively happy with the modern incarnations of C-style langs, e.g. Rust, Crystal, ES6, and I would be very unhappy if I had to chuck that in tomorrow and write lisp).

Generally agree with your train of thought on abstractions, and see how you arrive at macros. But my take is that it's simply not a good idea to go as far as macros, especially when the syntax sacrifice is so great. If the language is truly missing something, take it up with the language authors.

(I've never encountered a situation in > 6 years of writing Python where I encountered something I could not do and needed macros for).

Again, my basic opinion is I see it as a trade-off, and one that I don't agree with. I think many others feel the same way if they get as far as understanding the purported benefits of homoiconicity. But most aren't willing to argue it on Hacker News and get downvoted to high hell (which is why I used a throwaway).

(I should note meanwhile in another thread someone wrote off the entire JS community as 'full of self-righteous assholes' [0] and got upvoted to top post).

Thanks for the discussion, it was an interesting experiment in arguing these thoughts I've had for a long time, as an 'abandoned' Clojure-curious dev. I'm a bit sad I didn't get any new answers, but appreciate the insight all the same.

[0] https://news.ycombinator.com/item?id=12482176


Ugh. I am always annoyed by people who hate on JS.

As for syntactic "sacrifices", they're necessary for the most complex macros, not so much for the simple ones: rust has macros, too!


No, what he's saying is that a good team knows when to use the guns, and how to aim them. A good OO team knows how to layer their types, how to relate them, and when to stop. A good asm team knows how to handle nonlocal jumps, when to use them and when not to. Every paradigm has its guns. You're just paying extra attention to the lisp ones.


> You're just paying extra attention to the lisp ones.

Because people in this thread are attempting to put-forward lisps' macros (and writing code as an AST) as some big win, without acknowledging that it comes with a massive cost. Lisp-y, inexpressive, paren-soup syntax. I'm saying myself (and presumably many others else lisps would be more popular) don't buy the trade-off.

If you like it, and your team like it then great. But don't act like it's definitely win-win, and people who choose not to use lisps just haven't achieved enlightenment yet.


I never said you haven't acheived enlightenment...

But Lisp's syntax is actually pretty expressive and nice, once you get used to it, IMHO. You continually deny this, and while you're free to disagree (many do), you continually argue that the mine and other's opinions are Bad and Wrong. I find this frustrating.


I'm not arguing your opinions are anything other than your opinions.

I don't agree with them however.


Thank you for respecting my opinions, then.


This feels like it's fallen back into a more boring criticism: you just don't like Lisp's syntax. Ok, fine, everyone has a criticism about every other programming language's syntax. Maybe I hate Python's mandatory indentation. These kinds of criticisms are not really the most interesting ones though.


I don't think you can put those things on the same scale. I think it's telling that you dismiss Lisp's syntax like it's unimportant rather than acknowledging that it's the several hundred lb elephant in the room whenever it comes to talking about lisps.

What I'm arguing is that Lispers say that all the parens and lack of other, more expressive constructs are worth it because you get macros. I'm arguing that's a bad trade-off, because 1. that's a big sacrifice, 2. macros aren't a win.


Well, the macros and read macros mean that you can "get back" those constructs.


GPP asked what lisp's got that Ruby doesn't. I told him. Lisp isn't the end-all be-all. If you can't trust your teammates, than maybe you shouldn't be using Lisp. But keep in mind that you can write INTERCAL in any language. Lisp just makes it easier, because Lisp does its best not to limit you.

Besides, who would invent their own language in macros for no reason?

Also, the syntax is one of the best parts about Lisp. It's agressively regular, so you'll never have to worry about screwups or ambiguity. And with readtables, the syntax can be more expressive than you might think. Although you did set the standards pretty low...


> Also, the syntax is one of the best parts about Lisp.

I think you're in the minority there. I would hope most people agree that Lisp 'sacrifices syntax in the name of homoiconicity'.

If not, then we have different definitions of syntax and I'm not sure any discussion on that is likely to go anywhere.


In the case of code that works with code, rich syntax is a problem, not a solution. Getting rid of tons of syntax is not a sacrifice at all, when you're wrapping, transforming, and/or generating code. And as I've stated elsewhere, the fact that you can do this now so easily means it opens up that power for quick little inline tools, where in most other languages that sort of thing is reserved for major undertakings.

It also eliminates many syntactic bugs, makes searching & editing code easier for humans, makes editor tools easier to create and more broadly useful when they exist, and eliminates ambiguous precedence completely.

You begin to notice that syntax itself really is an anti-feature, after working across a wildly varying range of languages and styles, and designing many fully custom DSLs.


> Getting rid of tons of syntax is not a sacrifice at all

Getting rid of syntax that usefully expresses common cases absolutely is a sacrifice. What was that Turing tar pit thing about making things of interest easy? Lisp is powerful, but its lack of syntax makes it inexpressive.

> It also eliminates many syntactic bugs

How so?

> makes searching & editing code easier for humans

I disagree, code that lacks basic syntax such that programmers end up writing macros, does not make code easier to search and edit.

> makes editor tools easier to create and more broadly useful when they exist

I can see how that would work, because you're writing ASTs like you're a robot, which of course machines can understand better, just not humans.

> eliminates ambiguous precedence completely

That is a totally overstated problem. I've literally never been bitten by this in about 10 years of programming. Everyone in every other language just adds parens by default.

> You begin to notice that syntax itself really is an anti-feature

Having given Clojure I think 'a fair go', I just don't see that happening. Being able to read my teammates code will never be an anti-feature.

I just don't agree with the trade-off ...


Here's the fundamental problem: Not just humans read & write code. Our code also reads & writes our code. If you optimize for the nebulous "human" case, you end up walling off the possibility of having your software join your programming team.

Yet, humans can easily learn both styles. Whatever non-Lisp language you pick is as equally ununderstandable gobbledegook to non-programmers as Lisp. Even COBOL or SQL are way too explicit and finnicky for untrained, non-technical users to write complex applications. There's no difference in technical human understanding barriers between heavily magic-syntax'd code or explicit regular AST-like code. All it is is how used you are to one thing and not the other. (Rich Hickey's notion of the subjective "easy", as opposed to "simple")

Because most languages have traded off for the human-only case, having code writing code isn't even on the radar for most people. Write a few tightly-entwined code generators, and you'll start to see the barriers you hit. Full integration of these concepts opens up completely new productivity and clarity for both you the programmer, and your programming-assisting code, without any difficulties besides the programmer's arbitrary syntactic preference.


if your teammates are careful, and don't go overboard on macros, than their code will be just as readable as it would be in any language. But that's true of any feature. s/macros/(splitting the program into functions|using gotos|building a class hierarchy)/. You can write INTERCAL in any language.

And you keep talking like it's a tradeoff. For you, it may be. But for many of us who write Lisp, the paren-laden syntax is actually enjoyable: it's unambiguous, easy to write so long as the parens match up, and easy to read if you've indented it properly. And if you've got a half-decent editor, it'll indent and match parens for you. With Emacs and paredit, it's even nicer than many other languages: you'll be flinging sexprs around before you know it :-D.

Futhermore, you seem to think 90% of Lisp is macros: in fact, I hardly ever write them. I'm glad they're there when I need them, but when I don't, I write perfectly ordinary code just like anybody else, except that it's more pleasant to write because there's faster iteration, better tooling for manipulating code, and a nice, regular syntax that lets me forget about it, so I can focus on the parts of my code that matter, and get s#!t done.


I don't know ruby, so I can't answer your question directly. But I can ask you one: why not just learn a lisp? You don't have to learn a whole language to know what makes people like it. Working at it for a week or two, in you free time, would probably be enough for you to settle the question permanently. Lisp doesn't have to be a search for nirvana.


Maybe one of these days... I've been spending what time I have for learning completely different languages on Haskell, and I haven't messed with that in a while either.


The software done by these guys is pretty significant and manages a few of the European train systems.

http://www.siscog.com


FWIW Elixir has first class macros that go way beyond Ruby's metaprogramming, so you may want to try that.


Eric did a very good job predicting the direction of Ruby and Rails back in 2005, as well as the reasons why people chose them as tools over what existed at the time.


The poingant guide link (implimentation of the "belongs_to" class function) is a string of redirects. Anyone have a copy of the original?


http://poignant.guide/book/chapter-6.html#section3

edit:

While I love _why's guide, which can be very useful for learning ruby metaprogramming (and ruby in general), the technique presented there is a bit outdated. Modern ruby doesn't require such an "ugly" style because we now have Object#define_singleton_method[1] as a way of defining class level methods. There shouldn't be a need to use #instance_eval to define methods in the metaclass.

[1] https://ruby-doc.org/core-2.2.2/Object.html#method-i-define_...


I fail to see how it can be an acceptable Lisp when it lacks the AOT/JIT compilation as part of the standard toolchain and the development productivity of Common Lisp environments.


I don't have the love of CL development environments you do, but yeah, as a schemer, ruby's perf is kind of awful. We've got AOT, JIT, bytecode, and literally decades of research spend making these languages fast, and Ruby doesn't have that, or the hot code loading and quick iteration cycle that makes Lisp/Scheme realtime development so deliciously sweet.

To say nothing of our macro superpowers. But then, CLers still insist on on keeping the shotgun hovering over their foot, and insisting it's the only way to go about writing macros. Syntactic Closures and Implicit Renaming are both pretty cool. You guys should check them out.


I hardly have used Lisp besides Emacs Lisp and Clojure, but as a language geek I do have my collection of Lisp related stuff, mainly Xerox PARC, Lisp Machines and commercial CL environments.

Scheme also has environments like DrRaket, even if the language now has moved beyond its Scheme origins.


I don't know how you got a lispm, but nice for you :-).

I was confusing you with Rainer (who, by coincedence, goes by the username lispm on HN), who actually loves CL environments.

As for me, I set my store by Geiser. Integrated documentation from all the implmentations it supports, repl (as well as the capability to connect to a remote one), support for all three big Schemes, is an emacs extension, and has excellent docs? Consider me sold.


I wish.

I meant in what concerns papers, videos and magazines articles back from those days.


...Ah. Yeah, I wish I had a lispm too, but I don't even have the more pedestrian antiques.


This is a pretty bad article. The part that makes Lisp unique is hardly lambdas (every language has them, even Javascript), or being functional, but rather stuff like homoiconicity (and therefore macros). And once one scrolls down to that part, it's full of ignorant statements like "the most common use of LISP macros is to avoid typing lambda quite so much". It demonstrates a fundamental misunderstanding of macros, because they're executed at the compilation step, before the program is run. It's a zero-cost abstraction (wrt performance). Not that anyone writing in Ruby cares about that.

Look at this example from the article:

(defmacro with-each-natural-number (n expr) `(each-natural-number (lambda (,n) ,expr)))

It assumes that the programmer already wrote a function each-natural-number that does the required functionality when another function is passed into it. That's the Ruby way, because it only has functions, not macros. A good Lisp programmer would never write such inefficient code. Why create a local function here when you can easily write a macro that expands into a loop with expr as its body?

So, indeed, if you write in Lisp as if it were Ruby, then Ruby is an acceptable Lisp. If you write in Lisp as it's supposed to be written, then Ruby is ridiculously restrictive, and slow.


Actually, that example is fairly idiomatic, it's called the "call-with" style.

From Google's Common Lisp style guide ( https://google.github.io/styleguide/lispguide.xml#Macros ):

  You should follow the so-called CALL-WITH style when it
  applies. This style is explained at length in 
  http://random-state.net/log/3390120648.html. The general 
  principle is that the macro is strictly limited to 
  processing the syntax, and as much of the semantics as
  possible is kept in normal functions. Therefore, a macro
  WITH-FOO is often limited to generating a call to an
  auxiliary function CALL-WITH-FOO with arguments deduced
  from the macro arguments. Macro &body arguments are
  typically wrapped into a lambda expression of which they
  become the body, which is passed as one of the arguments of
  the auxiliary function.


> when it applies

One of the problems here is that the function does get an anonymous function passed. That means the function can provide dynamic scope via constructs like CATCH, LET with dynamic bindings, UNWIND-PROTECT, and so on. But it can't rewrite any code in the passed function.

This can only be done in a macro.


Fair enough, I was just responding to this:

> A good Lisp programmer would never write such inefficient code. Why create a local function here when you can easily write a macro that expands into a loop with expr as its body?

The nice thing about the call-with style is that it makes it easier to redefine your macro's functionality without recompiling all the call sites.



That's a bit needlessly insulting, but the line in the article about Lisp being functional is telling: Most lisps are emphatically not functional, by any definition. Some of it was kind of right, though: Markabye, in particular, looks like the classic lisp HTML generator macro.

And hey, most of my Scheme code barely has any macros (so call my idioms a decade out of date, we have a macro system equivalent to CL's, and I've never felt much need to use it. And yes, most Schemes do have a CL-equivalent macro system, it's just nonstandard), and I still love lisp.

Lisp has many, many, many, cool advantages, but I still think never having to worry about syntax or indenting style is the greatest one: people underestimate just how much the little things matter. :-)


Ruby is the lispification of Perl, according to its creator.


No, It is not acceptable.




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

Search: