Live data from Hacker News

The Nature of Lisp

defmacro.org

71–80 of 82 posts

Re: The Nature of Lisp

#71
post #63

Reading the related article on writing a Lisp interpreter in Haskell ( http://news.ycombinator.com/item?id=4764088 ) reminded me of my second blinding moment of enlightenment- understanding vau expressions. Things that can't be implemented as functions are typically things that require controlling the evaluation of arguments (conditionals, assignment, short-circuiting boolean operators, etc.), and additional language…

A language that is lazy by default lets you control the evaluation of arguments. Ordinarily, they're not evaluated, and if you force them they are. However, macros are not just about whether to evaluate -- but about exposing the internal syntactic structure of the arguments. In Haskell, using laziness you can implement control flow, short-circuiting, etc. If you want functions that work with the syntactic structure o…

Without access vocal inflection, I'm not sure if you're intending to argue, or expand. So, I'm gonna go with continuing to expand on the point.

Simple laziness does not allow you the same level of control over evaluation as vau expressions do. A vau expression can choose to evaluate it its arguments exactly once (like call-by-value), exactly as many times as they're used (like call-by-name), only if they are used (like laziness), as many times as you feel like, in a different environment than the calling context, or not at all, and can make that decision independently for every argument.

In Kernel's implementation at least, unevaluated operands are AST types that can be poked and modified, not opaque values like lazily-evaluated operands. As a result, vau expressions can be used to implement macros, both the hygenic and non-hygenic variety, and the language need not define quoting or quasiquoting because those features can also be implemented within vau expressions.

Vau expressions seem to play havoc with static analysis, though, so there are good arguments for actually having some of those things as built-in language features rather than just building everything as a standard library.

Re: The Nature of Lisp

#72
post #63

Earlier quoted context omitted.

A language that is lazy by default lets you control the evaluation of arguments. Ordinarily, they're not evaluated, and if you force them they are. However, macros are not just about whether to evaluate -- but about exposing the internal syntactic structure of the arguments. In Haskell, using laziness you can implement control flow, short-circuiting, etc. If you want functions that work with the syntactic structure o…

Without access vocal inflection, I'm not sure if you're intending to argue, or expand. So, I'm gonna go with continuing to expand on the point. Simple laziness does not allow you the same level of control over evaluation as vau expressions do. A vau expression can choose to evaluate it its arguments exactly once (like call-by-value), exactly as many times as they're used (like call-by-name), only if they are used (li…

I was expanding (with a slight correction about macros doing more than just controlling evaluation).

Haskell-style laziness comes with purity, where it does not matter much whether something is evaluated once or many times. It does matter if it is evaluated 0 or more though (due to non-termination and exceptions).

The opacity of values is what I meant by macros also exposing the syntax as opposed to just controlling evaluation.

Re: The Nature of Lisp

#73
post #62
post #43

I passionately hate XML so this could not possibly resonate with me. I never had the enlightenment he talks about. Actually I think that learning Lisp/Scheme might have made me a bit of a worse programmer in a way. It made me "dread" repetitive code so much to the point that I almost could not do anything with any language that's not highly dynamic. Anyways. I had 2 epiphanies with lisp. 1. Macros. Very powerful conc…

Interestingly, Oleg Kiselyov recently argued for not including full, undelimited continuations in new languages - http://lambda-the-ultimate.org/node/4586

That's not a problem though: delimited continuations are more expressive than undelimited ones anyway and often are a more natural way to solve programming problems.

Re: The Nature of Lisp

#74
post #43

I passionately hate XML so this could not possibly resonate with me. I never had the enlightenment he talks about. Actually I think that learning Lisp/Scheme might have made me a bit of a worse programmer in a way. It made me "dread" repetitive code so much to the point that I almost could not do anything with any language that's not highly dynamic. Anyways. I had 2 epiphanies with lisp. 1. Macros. Very powerful conc…

Re: macros, this is why tool support for macros is important. Many good Lisps come with macro debuggers that let you reason about the macro expansion. A good example is Racket's macro stepper: http://www.ccs.neu.edu/racket/pubs/cf-sp09.pdf

Re: The Nature of Lisp

#75
This is my third time reading this article; this time I stopped reading after a few paragraphs, but still skimmed it to refresh some things in memory. This is very good article, and one I would recommend to anyone to read, were it not for it's length - these days I guess half of the responses would be "tl;dr", sadly.

It's one of the articles that convinced me to take a look at Lisp a few years back, among others, which caused me to learn Scheme rather than Common Lisp or Emacs Lisp (I think Clojure was not around then yet). I invested half a year time to learn PLT Scheme/Racket and felt enlightened quite a few times along the way. First class continuations were the most mind blowing thing and I spent a few weeks trying to understand them. To prove to myself that I know what call/cc (or rather - it's delimited brethren) is all about I wrote python-style generators using them and this was one of the most rewarding experiences in programming for me.

Then I moved on, to Erlang IIRC, which was much easier to understand and use after being exposed to Scheme. In the following years I learned many more languages, all the while aiming for "purity" of the concepts and knowing full well that I won't be able to use any of them in real world. Many programmers would call Smalltalk a toy language - at best - but I had great time learning it and expanding my views on OOP, for example. I thought that the compromises that widely used languages make cause these languages to represent only a piece of what is possible, even if they are called "multi-paradigm", and wanted to explore more.

All this time I was writing commercial software in Python; I can't say if other languages I learned made me a better programmer - from the business perspective - but some really helped me expand my understanding of what I do. Forth and Lisp and Smalltalk did this and I was perfectly happy with stopping to use any of them after gaining some "enlightenment". They were not practical, not made for real world, they were there just to prove and demonstrate some kind of point, perspective.

This past week I couldn't work due to health problems and suddenly, after a few years of almost continuous work, I found myself bored. I thought, hell, why not? and went to reimplement a tiny bit of what I was working on earlier. I did this using Racket, my first "esoteric" language, so I had quite some things to relearn (good thing, too, because the language evolved in the meantime), but I finally (8 hours or so, in one go... they tell me it's not healthy to do this when you're ill, but it was fun) did it.

And it worked. And looked great. It was much shorter, more elegant and performant than Python. Certainly, half (or more) of this improvement came from me implementing the same thing the second time; but still, I was amazed at how easy and fun that was.

So the next day I decided to create another piece of code in Racket, this time a fresh one, which output would go straight into the larger system at work. It's good I had a task at hand which could be broken into pieces that small. And again, it worked, I did it in about the same time I would do this in Python, despite the lack of "concurrent.futures" or even thread-safe queue in Racket. I didn't use continuations or any other obscure features; just higher order functions and a few macros here and there to simplify error handling and such and some conveniences over pairs and lists.

I'm not sure what should I think about this situation. It's not a "proof of suitability" for the real world, of course - I'd need to write much more code to even begin to be able to claim that Racket is ok to use at work. But on the other hand I felt bad for ignoring really good language and environment for such a long time. I should have been trying to use it more and more often and I didn't because I thought it's not meant for that.

But above all, it was fun. Not because I was learning new stuff, like the first time, but because the language made it fun. And, what's almost as important, it worked - I have the code that does what it should be doing.

Well, I plan to try using Racket much more often from now on... Maybe someone else will give some Lisp a chance after reading this :)

Re: The Nature of Lisp

#76
post #45
post #43

I passionately hate XML so this could not possibly resonate with me. I never had the enlightenment he talks about. Actually I think that learning Lisp/Scheme might have made me a bit of a worse programmer in a way. It made me "dread" repetitive code so much to the point that I almost could not do anything with any language that's not highly dynamic. Anyways. I had 2 epiphanies with lisp. 1. Macros. Very powerful conc…

That "in practice" makes it sound like macros are so hard to understand that they're not worth using in real applications, which is definitely not true. Between the facts that (a) one uses them in deliberately restricted ways, (b) one gets increasingly familiar with them, and (c) they are are, token for token, way more powerful than ordinary code, macros end up being used a lot.

Certainly having experience with Lisp/Scheme will make it easier to deal with Macros.

As a "newbie" to Scheme, (well, actually what I played with was Arc, but I think it belongs to the Scheme family) I was able to write a few macros, and seeing them work in action was very nice indeed.

The tricky part is maintaining the macros or changing their behavior. It's like that saying goes: if you write code as cleverly as you can, you are not smart enough to debug it, because debugging is twice as hard.

Re: The Nature of Lisp

#77

It would seem to me that some variety of Lisp would be the ideal candidate as a sort of runs-everywhere language, a thin portable base language that runs on top of different runtimes, offering easy integration with whichever it is running on. Basically, something like a minimalist Clojure but not just for Java. It would be able to run atop the CLR, JavaScript or the Objective-C runtime as well. The interface with the…

Clojurescript compiles seamlessly to javascript, which has become almost universal. It provides a beautiful, battle-ready lisp.

Re: The Nature of Lisp

#78
post #25

Earlier quoted context omitted.

So, homoiconicity is a trifling, meaningless similarity? "Sure, it may be homoiconic, use prefix notation, have first-class functions (in additional to all the other usual functional paradigms that aren't unique to lisps) but it's not a lisp." Big ok to that one. This must be pedantry of the highest caliber, not ignorance.

They are not homoiconic. The underlying datastructure for many schemes, and racket, is not a list. It is a syntax object. Of course you can still do metaprogramming with syntax objects but I wouldn't call it the same thing.

You should be careful here, and not lump together "many Schemes" and "Racket" (or other specific Scheme implementations). The thing is that Scheme standards have traditionally avoided tying the language with a macro system that requires some specific representation for syntax -- giving you only the simple rewrite rules system means that you don't actually need to know that representation.

In Racket, OTOH, there are definitely syntax objects with enough functionality to write code that handles them, and I suspect that you know that. The question is whether this should be considered "homoiconic" or not, but this is a kind of a subjective issue, since at an extreme, I can say that all languages that have strings are homoiconic. Perhaps you need more from the language to make it so, maybe eval, or maybe actually require it to have compile-time procedural macros? In any case, Racket will have all of the features that CL does, so it is arguably at least "as homoiconic" as CL is. But in fact, it has more than just s-expressions: these syntax objects are basically sexprs + a bunch of stuff like source location and lexical context, so in fact they represent more than what lists in CL do. Should I then conclude that Racket is more homoiconic than CL? And this is not a tongue-in-cheek argument: in fact, many CL implementations are aware of the limits of sexprs as good representation for code, and add things like source location via a backdoor, like a hash table that maps pair objects to additional properties. Racket does that in its basic syntax representation so IMO it's fine to indeed consider it more homoiconic. And I also say that for the addition of lexical context information -- that's something that is not only included in the Racket syntax object, it's something that you just cannot get in CL, so if homoiconicity is being able to have a high-level representation of code (unlike raw strings), then this is another point where Racket wins the pissing context.

Finally, it's not that all "many Schemes" are limited as described above -- there are many of them that have their own macro systems with similar syntax values, and that includes Schemes that follow R6RS since that dictates syntax-case which comes with them. It just happens that Racket is has been traditionally running at the front lines, so it's more advanced.

Re: The Nature of Lisp

#79
post #65
post #7

Earlier quoted context omitted.

Syntactic abstraction usually requires a language change. For example, Python's "with" statement. In languages with macros, you don't need to wait for anyone to change the language because the entirety of the language is constructed from a few special operators, and you have the ability to continue constructing. Like Python, Clojure has a "with-open" macro. If Rich hadn't already added it, you could build it yourself…

In Haskell, there's the "bracket" function with generalizes the "with" concept. withFile fileName mode = bracket (openFile fileName mode) hClose Then: withFile "/usr/share/dict/words" ReadMode $ \h -> do contents No need for macros for this. Just passing anonymous code blocks easily. Interestingly, the type of withFile, after its given the filename and filemode args is: (Handle -> IO a) -> IO a Which is the type of a…

My comment intended to show how macros can allow one to create syntactic abstraction. That one can accomplish X without creating new syntactic abstraction, or that some language already has syntactic abstraction for X, is wholly irrelevant.

Re: The Nature of Lisp

#80
post #65

Earlier quoted context omitted.

In Haskell, there's the "bracket" function with generalizes the "with" concept. withFile fileName mode = bracket (openFile fileName mode) hClose Then: withFile "/usr/share/dict/words" ReadMode $ \h -> do contents No need for macros for this. Just passing anonymous code blocks easily. Interestingly, the type of withFile, after its given the filename and filemode args is: (Handle -> IO a) -> IO a Which is the type of a…

My comment intended to show how macros can allow one to create syntactic abstraction. That one can accomplish X without creating new syntactic abstraction, or that some language already has syntactic abstraction for X, is wholly irrelevant.

Well, I'm always looking for problems that are uniquely well solved by macros.

Lisps pay a dear price to have macros. If their capabilities are covered otherwise (in ways that are not as costly), why have them?

Post reply on HN