Why Racket? Why Lisp? (2014)
practicaltypography.com
practicaltypography.com
The last couple of weeks I've spent my days reading 'The joy of Clojure', Structure and Interpretation of Computer programs, lots of tutorials and documentation, playing around in the repl + experimenting with all kinds of frameworks and libs in clojure (eg. Om).
I've spent today implementing the brainfuck interpreter in Racket.
I can't explain it, it's like something is calling me - 'learn lisp. now.'.
Given the amount of Lisp code read/written and the relative novelty of it, I'm dreaming lisp code and the arguments to functions are actual physical things, which are then mapped, reduced, recursed or expanded.
Literally, I think I'm going crazy.
I can fluently write x-platform C++, Javascript/CoffeeScript, Objective-C, Java, Pascal, VB and everything in between.
But never have I experienced this kind of mental strain/obsession as I do now with lisp.
But.
Seems like I'm not alone! Given the amount of lisp news lately on HN, I fell like more and more people are going through what I'm going.
Seems like we here on HN follow a common mental pattern and suddenly everyone's talking/learning lisp.
As interesting as learning lisp is, this 'group preference' thing is even more interesting to observe.
LISP's also has a long history of popping up all over the place because they are extremely implementation-friendly (if you want to write a simple language; if you want to make things fast they get trickier, thought by no means impossible) given the simple syntax.
So you'll also find a lot of people (like me) who don't really like LISPy languages, but who still end up dabbling with related technologies occasionally (my very-slowly-in-progress Ruby compiler uses s-expression syntax (though no LISP semantics to speak of) to express a tiny language to implement some of the lower-level plumbing to bootstrap the Ruby core classes, for example)
Much like Fortran (no longer FORTRAN), the acronyms became proper names in their own right.
CL-USER 30 > (eq '|LISP| '|Lisp|)
NIL
Common Lisp knows very well the difference between LISP and LispThis is a LIE.
I get tired of hearing this over and over. Broken Lisps are easy to implement.
Try implementing a real s-expression parser in C. I'll wait.
For example, this library: http://sexpr.sourceforge.net/ takes 5000+ lines of code. And it doesn't even handle dotted pairs! (For good reason, handling dotted pairs without recursion is extremely difficult.)
I have written entire languages in that number of lines. So, have many others.
Lisp does not count as small.
At just a glance, it appears that the library is slurping the expression and avoiding recursion instead optimizing for speed, memory usage, and error reporting, but it seems like you could implement a recursive descent parser if you weren't as concerned with those. If the author took their same approach to parsing s-exprs for a grammar like C, I'm not sure how successful they would be.
I haven't really looked at grammars / compilers in a long time, but I'm curious if I'm off-base in thinking s-exprs are relatively easy to parse. Some grammars like brainfuck or assembly would obviously be easier.
Really? Try it in C.
The Scheme interpreters do very limited dotted-pair parsing until they bootstrap into scheme. There is a good reason for this.
And this is before we even start dealing with things like circular references.
I still do C#/Javascript/Java/whatever in my day job, but LISP is just...sexy.
And for those that hate parens - Emacs paredit. Nuff said.
If you include the curly braces, I'm not sure C/C++ has fewer parens than Lisp does, to be honest. Not much fewer, anyways.
function {} is more familiar mentally than (function).
I don't like the dark side, I hate to see all the beauty I create when I'm 'high' dribble through the impotent hands of the dark periods. But I've come to accept that this is how I am and always will be.
My only way out of the hole is to try and steer clear of addictions and learn new stuff.
Since I can't do anything of practical use during this period - nothing is exciting, the world is going to hell, I'm a failure and cannot do it, etc, I use the time to learn new stuff, read books on every possible subject and just try not to be too big of a drag on those around me.
I've been here multiple times and I know that each 'down' period is followed by a rush of energy and creativity and I have to be prepared with knowledge and health so that I can apply it during my 'mental spring'. This is how I've created great stuff before and I will in the future. At least this is my hope.
At the moment, learning lisp is a kind of psychotherapy that I apply to myself and it seems to be working, even though I have no idea what to do with that knowledge, it will soon all make sense.
Brilliant is impossible to be, what we can hope for is moments of 'brilliance', where one can come up with original solutions to problems. Ideas, revelations..
I do have those from time to time and it feels great.
But as a human being, it does matter what and how you feel in between those moments of brilliance.
And during a 'low' phase (which can last months, day after day), all your moments of brilliance are replaced with mental blocks, melancholy, cynicism and pessimism about everything and everyone. It's a very bad place to be, it's painful, the feeling of being a fraud, the way you disappoint yourself and others by not following through, the way you see your dreams and hopes drown in a sea of confusion and frustration. When everything you've earned/gained is slowly erased and you have to start again from step 1.
Yeah, I think that's a problem. You either kill yourself, drink/drug yourself to death or find a way to cope with it. My solution is to just obsess on books/studying new stuff when the mental winter comes and try to stay away from the easy escapes.
That's why it's so valuable as a right of passage. If you've spent your career writing simple iterative imperative loops, suddenly you're a newb again with map/filter/reduce this, partial that, and recursion and lambdas and closures and finally after your 20th time staring, dazed at 3 measly lines of nested composed recursion - it all starts to flow, and boom, you have a new way of approaching every problem.
And even if you don't live in the lispy functional realm after that you'll always be better off for having crossed that threshold.
Knowing me, though, I'll probably just recklessly abuse hygienic macros until the sun burns out.
Right now I'm converting some old Clojure code from a couple of years ago into Haskell, and I much prefer the Haskell static type checking to the dynamic typing of Clojure. I still have a lot more to learn to get my Haskell powers up to what I could do in Clojure, but I think it's worth the work.
At the time, although I could read the material, it seemed to require too much mentally to actually Get Things Done. I think it was a deep misunderstanding about how to structure a program that is independent of state.
Later, after playing with Scala, amongst other languages, I came to understand that state exists in functional programs. It's not conjured up out of nowhere, spontaneously brought into existence, but rather, it exists as data, and it is the job of the program to direct the flow of data appropriately - like a plumbing system, so to speak.
You begin thinking "naturally" in Lisp because there's no syntax to memorize, it's all data and / or semantics. You can literally write the code in your head. Languages I've used for years and years don't do this to me like Lisp does.
It also made me feel more confident about programming in other languages and not to get distracted by "fancy" syntax or features that are just warmed over scraps from the floor of Lisp's feast on the table.
Since that statement occurs in a section about Racket languages, I should probably clarify. Arc isn't technically a Racket language—that is, it doesn't use any of Racket's facilities for defining new languages. It compiles to Racket (and so you're all using it right now), but has its own distinct implementation.
Historically, Arc wasn't built on top of Racket. I think pg may have started on Common Lisp, then went to Scheme 48, Mz Scheme, and so on. Probably the most accurate thing to say is that Arc was built in pg's head after years of thinking about Lisp. It doesn't have a particularly close relationship to any of those underlying platforms. Semantically it's closest to Common Lisp, but don't tell pg I said that.
http://www.reddit.com/r/programming/comments/bstl/arc_lisp_d...
A friend told me he used to track developments in Arc by noticing when pg had switched mailing lists. :)
"I wish they had implemented Arc as a Racket module language since then you could actually develop in DRracket, debug, and make executables."
Our own kogir actually worked for a while on implementing Arc as a Racket language. It would have some advantages, especially around tooling. However, like all such platforms, Racket has a sweet spot (what's easy to build on top of it), and the further your semantics are from there, the harder it quickly gets. Redefining how null, true/false, lists, and macros work—as Arc does, relative to Racket—is not how a language platform is meant to be used! You end up fighting with it in a way that loses most of the advantages, and the difficulty seems to grow asymptotically as you approach 100%.
What you want instead is to stay in the sweet spot and let the underlying platform nudge the new language in all the directions that are easy to implement. The same is true of building transpilers—it's an order of magnitude easier when you can piggyback on the semantics of the underlying language. But this is not an option if your source language has already been defined elsewhere.
However, just because Arc can't be a layer of macros on top of Racket, that doesn't mean that the language can't be implemented as a Racket language. There are Racket language implementations of Algol 60, Python, and JavaScript, all of which are very different from Racket, more so than Arc is. And those implementations get the tooling benefits that Arc today misses.
It's easier to write a simple interpreter than a compiler, and Arc's current implementation seems to be working well for you, so far be it from me to tell you how to run your project. But certainly it could be implemented as a Racket language, no matter how far away it is.
http://www.greghendershott.com/rackjure/
Adding the if statements wouldn't be too much fuzz.
As I allude there, Paul Graham’s writings about Lisp (mostly in Hackers & Painters) helped persuade me to explore Lisp languages. (Those writings have also persuaded many others.)
In particular, Arc's reliance on Racket persuaded me to take a serious look at Racket. So leaving aside quibbles about what “on top of” means — is Clojure not built “on top of” the JVM? Python “on top of” C? — Paul’s choice of Racket was influential in my choice too. (As it has been for many others.)
As for software being “built in [one’s] head,” that seems facially true of any software. The core thesis of “Beating the Averages” is that the tool you choose to get it out of your head and into the world matters. Having now had my own Lisp revelation, I not only buy Paul’s thesis, but I even think it could be strengthened: Lisp permits the implementation of a whole category of ideas that aren’t possible in other languages.
Moreover, Paul wrote that essay nearly 14 years ago. Since then, Lisps have gotten somewhat more popular (Clojure has led the pack). But as I say in the article, as a group, Lisps remain way behind the programming mainstream. So ultimately, my goal is not to evangelize for Racket and exclude other Lisps. I know Racket better because that’s what I use. But more people using all of them would be a great thing.
The total number of developers who know a Lisp may have grown, but the total population of developers is also rapidly growing.
Measured as a % of language popularity, I'd expect it is largely flat, despite awesome mass-media efforts like Seibel's Practical Common Lisp http://www.gigamonkeys.com/book/
These days with more regional conferences starting it's nearly in the twenties, and attendance at Strange Loop (which has had lots of keynotes by lispers) is several thousand and growing.
Have noticed quite a bit more buzz about Racket and Lisp very recently around here. It's always been something I mean to explore.
I'm already pretty good with Clojure, so can anyone who knows both languages well comment on the ups and downs of learning Racket after learning Clojure?
EDIT: Wrong link. This one might be interesting as well so I've left it here:
If you already have a solid development environment set up and have spent a lot of time learning project management tools, you might have an easy enough time getting started with Clojure, but getting started with Racket tooling is easy even for children.
Clojure is a lot better out-of-the-box for FP, but Racket has third-party tools (like Rackjure and fector) that do a lot to close the gap. On the other hand, Racket has pattern matching out of the box which is really nice, but you can get that in Clojure as well using external libraries. One problem that you can't fix in a library is Clojure's omnipresent nil; Racket does a much better job of ensuring sensible semantics in error conditions, partly due to the fact that pattern-matching is built-in.
Using either still keeps you in the Lisp universe.
Also, Greg Hendershott's rackjure library emulates a few "Clojure-inspired" ideas in Racket
https://github.com/greghendershott/rackjurehttp://okmij.org/ftp/Scheme/xml.html
There is a Racket "package" (not quite a port) of SXML and SXSLT:
[0] https://www.gnu.org/software/kawa/Android-view-construction....
It's a very simple idea, really: you write your document and sprinkle racket s-expressions wherever you want. You can put your definitions at the top of your document, or in another file. You describe your templates in Racket also, because like Butterick says in his RacketCon video [2], "S-expressions and XML are the same thing". You can use tags that you haven't defined, they just get placed in the resulting HTML.
You still have to roll your own CSS and any JavaScript. As far as I can tell it doesn't help you out with things like keeping track of footnotes numbering or citations and such: you roll your own for things like this, Pollen is no LaTeX. This is partly why I say it's a really simple system. It makes a certain set of web authoring things simple, but it doesn't try to be a one-stop shop, which is excellent because this makes it a supremely flexible tool.
It also has some backend fancy sauce where you can save your file and refresh the page [3]. Also I admit I'm not really a big fan of DrRacket: I've just been using it for the tutorials because I have no idea what to expect from Racket, but I'm slowly moving to Emacs, and Racket seems to work fine there.
Definitely worth checking out Pollen if only to get a light introduction to the thoroughness of Racket documentation (they definitely do things differently in Racket-land!), but stay for the tools you need to roll your own ultimate static blog generator.
(Also, completely unrelated: cool video and cool title at another RacketCon 2013 talk: "Racket on the Playstation 3? It's Not What you Think!" [4].)
[1] http://pkg-build.racket-lang.org/doc/pollen/
[2] https://www.youtube.com/watch?v=20GGVNBykaw
[3] I've made a request for no-refresh updates: https://github.com/mbutterick/pollen/issues/35
True, though you can automate those files (and any other text-based files) with Pollen as well, so you can use common functions and data across all of them.
Also, here's the previous discussion of Pollen on HN:
I think Pollen's biggest strength is that you can reprogram its markup -- as its documentation puts it, you can attach behavior to tags. I haven't taken much advantage of that yet, but you can do things like create a "TOC" tag that builds a table of contents by inspecting child documents and looking for h1 tags (or looking for, say, "chapter" tags, which you've defined to expand to "<h1 class='chapter'>"), or inspect the contents of paragraphs and subtly shift the first line margin to the left if the first character is a quote mark (which Butterick's Practical Typography does). You could replicate some of that with a template language that allows user-definable tags, but I don't think you could do all of it.
Its biggest weakness, at least for me, has been finding a pleasant workflow. Despite having a built-in web server it feels kind of clunky compared to other static site generators. You're largely on your own for writing a deployment script ("raco pollen clone" is not a valid substitute). The DrRacket IDE is virtually a requirement for Racket programming, but it sucks teabags for editing long prose documents; you'll likely find yourself working in one editor for Racket language files and another for Pollen source. This isn't necessarily a dealbreaker, but it's at the least annoying.
If you are an Emacs fan, you can use it for Racket instead of DrRacket using Geiser and Quack, which might solve a lot of problems. (I've never tried it -- I'm comfortable with Emacs, but I've never loved the way it handles prose rather than code.)
Hence, Lisp code is made of nested lists. When you think further about it, it means that Lisp metaprogramming facilities may do things just with plain list manipulation functions! So it's not about metaprogramming itself, it's that it's incredibly easy. This is what people refer as "code as data" and "homoiconicity" and so on.
Yes, all programming languages are programmable. But Lisp tends to be unlimited in its programmability, in almost the same way that Unix is: if you have root, you can change anything. I'm pretty sure that in most Common Lisp systems, you can redefine large parts of the compiler at runtime. And so on.
Syntactic macros are just one part. They can be extremely handy. Lots of Lisp systems use them well. Random examples: the DEFSYSTEM macro of ASDF; the DEFINE-EASY-HANDLER macro of Hunchentoot (a Common Lisp web server); the (controversial) LOOP and ITERATE macros; etc.
Another somewhat random example: Movitz was (is?) a project to write an x86 kernel in Common Lisp. It actually included its own compiler. It defines a lot of macros for instruction definition [0] and uses them in code that I don't understand anything of [1] but is probably very clear to someone versed in assembly.
Recently I've been trying to add some nice logging to a JavaScript program, and it's a typical scenario where the normal syntax is just annoying enough to make the code ugly and hard to scan—if I had macros, I could invent some other syntax.
How to use macros is a tradeoff that I guess comes with Lisp experience, but they can be a powerful escape when the standard syntax is annoying, and a way to define your own DSLs without restriction.
[0]: https://common-lisp.net/viewvc/movitz/ia-x86/def-instr.lisp?...
[1]: https://common-lisp.net/viewvc/movitz/ia-x86/instr-add.lisp?...
Lisp uses prefix notation. With mexpr[1] it uses infix notation.
And so on.
How would you write a program to write a program? How can you use this generated program in your other code? It's really hard to do with a lot of programming languages, even those that have eval like JavaScript, so it never occurs to people that something like a template system or a code translator/DSL should be trivially easy to write.
A given language makes certain ideas easier or harder to formulate, and thus easier or harder to have in the first place. So the language constrains not only how you think about your program, but what you think—and thus what the program itself becomes.
Platforms, communities, and culture also help determine these things, but they're not independent of the underlying language.
I have no doubt the difference among human languages and programming languages could be completely different, but is there any concrete example you could give of an idea that is easier to express in one programming language than in another?
> is there any concrete example you could give of an idea that is easier to express in one programming language than in another
Sure. Since this thread is about Lisps: writing code that works with other code is an order of magnitude easier in Lisp. Therefore people tend to do it a lot more.
Or, if you are not satisfied, try to create a non-synchronous TCP server in C, Java, or Python. Then write one in Haskell.
Try creating RAII containers in Java... Ops, that can not be done!
In the SICP lectures, when Sussman teaches the class the word "predicate", he mentions that knowing the names of these things is important because "As any sorcerer will tell you, if you know the name of a spirit, you have power over it".[1]
If you only know C, you're unlikely to think in terms of map/filter/reduce. If you don't know Haskell, you're unlikely to think in terms of functors and monads. You may be vaguely aware of repeated patterns, but knowing you're looking at a monad gives you a lot more power to reason about it.
What kind of code do you think people will be writing 10,000 years from now? What will they be able to do that you can't? What will they have that you don't?
"Will people still be doing X in the same way Y years from now?"
If not, what will make that change possible? What will drive it? How can you help it along? Is that a startup idea?
Java on the other hand, now that's a horrible language ;) ;)
If Javascript were only an ugly looking scheme...
Please name another programming language whose inventor has described it as having “a lot of stupid in it.”
It’s no longer a matter of fashion. It’s a matter of authoritative opinion.
(setf (char (symbol-name nil) 1) #\U)
(Though I guess that could be taken as being the same thing as doing nonsensical pointer arithmetic in C -- you simply should know better.)"non-conforming" means you will get different results in different implementation, perhaps something like a nasal dragon, or perhaps an error signaled, or perhaps some more benign behavior.
$ clall -r '(progn (setf (char (symbol-name nil) 1) #\U) (quote nil))'
CLISP Attempt to modify a read-only string: "NIL"
ECL Detected access to an invalid or protected memory address.
Clozure Common Lisp --> #|symbol not found in home package!!|#COMMON-LISP::NUL
Armed Bear Common Lisp --> COMMON-LISP::NUL
CMU Common Lisp --> COMMON-LISP::NUL
SBCL --> COMMON-LISP::NUL
Some implementations will gladly and blissfully modify the symbol name, and your program will break because it cannot intern the original name anymore (not the best behavior for an implementation IMO); some implementation will detect the non-conforming access. Worse could have happened (remember, you are on the Internet so you can be located by geoip/gps/wifi, and missiles silos can be hacked by botnets).But otherwise indeed in general, mutable lisp objects are mutable, and you can implement mutating algorithms as well as purely functional algorithm. Foremost, you can have an hybrid approach, using what's best to solve the current problem.
The only thing I'm not fond of Racket is that compared to CL w/ Slime it feels way less interactive, a step up from Python, but still not CL (nor Smalltalk)
But lets focus on the positive, the OP is highlighting that the trite criticism of non-lispers about parens is bogus.
There's setf and setq for assigning; nconc, nsubst, and the destructive list operations; rplaca and rplacd; etc.
It's easy to write Lisp that doesn't mutate (let), or use constructs that hide it (dolist, dotimes, etc.), but mutable data has been in Lisp since the beginning.
That said, there are languages in the family that don't allows mutation, but they're a small minority.
I appreciate the author's wanting a more explicit and practical answer to this question, but I don't think his answer here ("expressiveness") does any better job than the other explanations he criticizes.
> But [learning Lisp] also requires an investment of about 100–200 hours. That's asking too much.
No, it's not. That's how long it takes sometimes, or longer. Why do we all expect answers and understanding to come so quickly and easily? It'd be a nice world otherwise, but almost always enlightenment comes at a cost: patient focus and study.
At the other side of it, people giving you those recommendations really have no better way to explain it. PG has an well known essay about language power, but even that isn't very precise. Some kinds of knowledge you really have to know before you understand what they are good for.
And also, last time I looked at Racket, it seemed very focused on academics. I just wanted to write some simple Scheme in a .scm file and try it out (a script), but I think I had to choose a language first (maybe a line at the top of the file? Don't recall) --- the Racket ecosystem seemed off-putting.
Liking syntax sugar is hardly a reason not to use the language with the most powerful macros around. You can write macros to provide nice literal syntax for whatever you want - this is part of the fun.
The idea of everyone having to come up with their own syntax for such commonly-needed things as, say, hashes, seems counter-productive to me. That would lead to everyone's code looking like a different language.
You said you stick with Clojure over Scheme on account of some syntax. I said you can have that syntax in Scheme. You then said the non-sequitur "the idea I created in my mind of each individual user coming up with their own syntax is absurd", which I agree with. I don't know what to tell you but you seem confused. What you said is not a response to what I told you. I eliminated the difference you complained about, and you complained that in Scheme it's possible to solve the issue you have with it.
There's no pleasing some people.
Trying to please everyone—or convince everyone—is not a fruitful method of language advocacy.
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
and instead write a nice little main macro, so you may just write: (main (print "Hello, World!"))
Do that a few times in an application, and instead of 10 million LoC, you can write it in 100,000 LoC. This becomes much more maintainable!Also, see: Alan Kay "Programming and Scaling" 2011
http://www.tele-task.de/archive/video/flash/14029/
https://www.youtube.com/watch?v=gZmcmdsoAXU
https://www.youtube.com/watch?v=-UOmItPa4iA
https://www.youtube.com/watch?v=QlPavndhYxQ
https://www.youtube.com/watch?v=y9xLi0iJg1g
Also have a look at: https://groups.google.com/forum/#!msg/comp.lang.lisp/gsQJOGKYUw4/oLLHW0f4Ce4J
https://groups.google.com/forum/#!msg/comp.programming/FiNIiSm5cJE/JkF5wa6Ke54JAt that point, you may not find the macros to be so much of an improvement on the boilerplate...
At that point, you may not find Objects (aka copyable global scopes where instance variables are globally available to all instance methods of a class) to be so much of an improvement over symbolic computation.
Honestly, what you said never happens in practice (no one is abusing macros like that) but what I said is literally taught by Sandi Metz in her book Practical OOP in Ruby.
Your (common) reaction is just your everyday fear of the unknown. Try it.
Hash tables are commonly used in imperative programming, but they aren't needed so often in Scheme where we prefer to use a purely functional style. We usually use alists because they are persistent. When I reach for a mutable hash table, it's usually part of some imperative process where I start from an empty table.
Depending on the Scheme you use, there could also be a persistent hash table implementation. Guile has vhashes, and there's an alist->vhash procedure, so you can still use the alist syntax.
Might as well use `#lang racket` and get the already-existing regular expression literals, "batteries included" libraries, etc.
That said, for a variety of reasons, I would actually prefer to not be tied to the JVM.
Any somewhat modern Lisp has hash tables, vectors, etc.
Paul Graham's ANSI Common Lisp will get an advanced programmer up to an advanced level more quickly and efficiently.
That aside, I agree with your description of On Lisp.
Common Lisp really is a great developer's language, and PCL helps show why. Peter Seibel's a great writer, too.
[1] http://www.gigamonkeys.com/book/ [2] https://mitpress.mit.edu/sicp/full-text/book/book.html
On Lisp by pg explains very well what lisp is all about in the first few chapters.
1: http://learnxinyminutes.com/docs/common-lisp/
A more interesting and unusual section of the book is the intro to using Emacs with Clojure -- it's not necessary to use Emacs, but you will learn how to edit all those parentheses without worrying about editing parentheses. Even if you don't use Emacs, this feature (paredit) is available for many editors and IDEs, so it's worth checking out.
Point taken, but troll-mode pedantry: (x + (1 if is_true() else 2)) would be valid :)
For more on the per-
ils of taxing reader
patience, see
WHY DOES TY-
POGRAPHY MAT-
TER
This quote, hyphenated as is, provides all sorts of opportunities for snark. Instead I'll just note that it seems Pollen needs work. It's far too eager to hyphenate; doesn't protrude hyphens, commas, etc.; and doesn't use TeX-style paragraph-level optimization. Not being able to identify the bounds of hyperlinks is also a strong source of irritation. For more on the
perils of taxing
reader patience, see
WHY DOES
TYPOGRAPHY
MATTER.
This is what I get on Firefox and ChomeThe .Net world has IronScheme which is very stable and interesting too.
2 - List based = flexibility, get more done faster because lists are simple and everywhere
3 - Strongly nudges in the direction of functional programming
4 - Rocking documentation, advanced libraries
5 - The Dr Racket GUI IDE
6 - X-expressions (don't know what they are)
7 - Hygienic macros
9 - Easy to write powerful DSLs
10 - Many low hanging fruits to contribute