Hacker News new | past | comments | ask | show | jobs | submit login
Why I haven't jumped ship from Common Lisp to Racket just yet (fare.livejournal.com)
270 points by networked on Aug 23, 2017 | hide | past | favorite | 99 comments



The author, a famous and well-liked lisper, is not consider ing portability features. CL is an ANSI standard and code often runs with no changes in many distinct CL implementations/compilers/interpreters.

Also, related to that point: There are many different CL implementations out there that satisfy different use cases, like for example JVM deployment (ABCL), embedded systems (ECL), speed(SBCL), fast compile times (Clozure), pro-level support (LispWorks, ACL), etc. So the same code has a huge amount of options for deployment. It really makes Common Lisp be "write once, run anywhere".

Then speed is also never mentioned. Lisp can be seriously fast; under SBCL it is generally 0.3x to 1x C speed; LispWorks might be faster, and there's a PDF out there called "How to make Lisp go faster than C", so that should give an idea of Lisp speed potential.

CL wasn't created by philosophing about what a programming language should be for many years; CL was basically created by merging Lisps that were already proven in the industry (Maclisp, Zetalisp, etc), already proven to be good for AI, heavy computation, symbolic computation, launching rockets, writing full operating systems, etc.

CL is a "you want it, you got it" programming language. You want to circumvent the garbage collector? need to use GOTOs for a particular function? want to produce better assembly out? need side effects? Multiple inheritance? Want to write an OS? CL will deliver the goods.

In short, i would guess that from a computer scientist or reseacher point of view, Racket is certainly more atttactive, but for the engineer or start-up owner that wants to have killer production systems done in short time, or to create really complex, innovative systems that can be deployed to the real world, Common Lisp ought to be the weapon of choice!


"How to make Lisp go faster than C" (2006)

http://www.iaeng.org/IJCS/issues_v32/issue_4/IJCS_32_4_19.pd...


Reading that a bit, it makes me think about the current "push" in Python to add types whenever necessary...


The biggest issue I see with Common Lisp is that the standard is stuck in time, lacking stuff like database providers, a common FFI or threading.

It needs a review of the CL environments that survived to modern days and adopt the common extensions.


I think it is a pretty shallow view at standard. CL implementations grow and have agreed interfaces (or portability layers). A few examples:

- sockets via usocket

- threading via bordeaux-threads

- metaobject protocol via closer-mop

- foreign function interface via cffi

all these are not mentioned in the standard at all, but are implemented and used in production environment.

Having base standard and extensions which may be used portably (via portability layers) is better situation than having no standard (for instance python or ruby) and a reference implementation.


> Having base standard and extensions which may be used portably (via portability layers) is better situation than having no standard (for instance python or ruby) and a reference implementation.

Is it though?

I agree CL has (after slightly over 2 decades) worked out a standardization path for some core features that you simply cannot live without (e.g., usocket).

But all these variants are noticeably dated abstractions and in many cases the more modern expressions (e.g., threading vs more modern concurrency tooling) can't (or don't) use the compat libraries.

I'd rather have one good, crossplatform reference implementation than many competing but slightly incompatible implementations that only agree on very old standards. In that sense, I'd rather use Racket than CL.

It's true, letting go of some CL features is painful and you'll never get them back. CL's condition-restarts are an especially poignant loss in this modern era of Golang's almost comically regressive error handling schemes.

I'd like to think that if CL'er start to disperse more into other language communities that their gentle pressure and eloquent examples can help motivate other communities to adopt these features.


Good example of what you're talking about is lparallel library (check out lparallel.org) - it is modern concurrency library build on top bordeaux-threads. lfarm is example of another library workinng on top of usocket which does the same for distributed computing.


Which usually is what leads to feature expressions spaghetti.

Also, it means that a Lisp newbie won't be aware of what are the best libraries that are portable across Lisps for such features.

EDIT: typo correction (missing are)


Feature expressions shouldn't be used in normal applications, only in portability libraries. To use threads on any implementation which supports them (given it's supported by the recommended portability layer):

> (ql:quickload 'bordeaux-threads)

> (bt:make-thread (lambda () (sleep 1) (print "hijack")))

that's all, no feature expressions whatsoever. Quicklisp is a system manager (something similar to npm in sense that it helps you to download dependencies).

Regarding best libraries: isn't that true for any language? Newbie doesn't know good libraries, because he is a newbie. He has to learn which libraries are good and which are bad.

edit: and by each supported implementation I mean in fact all active complete Common Lisp implementations: ArmedBear, Allegro, clisp, Clozure, Corman, Embeddable, Man-Kai, LispWorks, Steel-Bank and Scieener.


I kind of agree with you, however the beauty of a language standard is being the point of reference for libraries and language semantics.

In any case, the Lisp like languages I use aren't Common Lisp (Clojure, Emacs Lisp, Script-Fu), so that was just an idea.


I think the point is you can't really have it both ways. CL prides itself on its extensive and portable standard library, but has huge gaps by the standards of modern languages.


> but has huge gaps by the standards of modern language

Please detail, because i'm sure that the Lisp community would love to fill those gaps. After all, Lisp is addictive to the point of resembling a hard drug.


Well, start with the stuff that OP's just been arguing doesn't need a standard implementation. :)


Only if portability is a goal. One can stick to one implementation and use the extensions provided. There are also portability isssues when people wants to use different python implementations!


> is that the standard is stuck in time, lacking stuff like database providers, a common FFI or threading.

You don't need database providers to be integrated in the language spec. Not in CL and not in many other successful languages.

We already have a de facto common, portable FFI: CFFI.

We already have more than one library to do threading in a portable way. Also, threading has been left out of the CL standard on purpose, so each implementation can offer different capabilities and you can choose whatever fits best to your problem.

> The biggest issue I see with Common Lisp is that the standard is stuck in time,

The standard is from 1994 and Lisp is still pretty much one of the most modern and advanced languages around; i would say it's the other languages, like Java, that historically get new 'versions' each few years because they can't be extended using the very same language, unlike in Lisp or other modern languages like Julia or Racket.


> You don't need database providers to be integrated in the language spec. Not in CL and not in many other successful languages.

Many people underestimate how useful Perl DBI, JDBC, ODBC, Python DB-API, ADO.NET are.

Which is why, in spite of all design flaws, even Go has a database interface defined on their core library.

> Lisp is still pretty much one of the most modern and advanced languages around

I agree with this part, and we are still far from the whole Symbolics experience.

Yet using threading as an example, since it is left for each implementation, it means one cannot guarantee portable semantics across implementations.

Exactly the issue we had with writing threads in C before p_threads came to be, and even as portable library there are semantic issues (e.g. signal handling) until C11 finally defined what threads in C are supposed to look like.


> Many people underestimate how useful Perl DBI, JDBC, ODBC, Python DB-API, ADO.NET

JDBC, ODBC, ADO.NET are not part of the language spec; they are separate specifications. Your initial argument was that the language spec ("the standard") is "stuck in time".

And on the other hand, database access is not really an issue on CL.

> Yet using threading as an example, since it is left for each implementation, it means one cannot guarantee portable semantics across implementations.

There does not exist a single, one-size-fits-all, valid-for-all-use-cases approach to concurrent programming.


> There does not exist a single, one-size-fits-all, valid-for-all-use-cases approach to concurrent programming.

And in CL there does not exist a common platform to efficiently build better abstractions in CL. TBF, this is only marginally worse than many other popular languages. Still, it's undeniably worse.


Looking at Python:

https://wiki.python.org/moin/PythonImplementations

There is a reference implementation, no standard, and a bunch of implementations and/or extensions. Is it really better? What guarantees do you have? If you need to provide an extension, you have to discuss about extensions and push patches to CPython, just like you could push patches to SBCL or ABCL.

But at least (which I do not consider "undeniably worse"), CL is its own platform and offers a standard extension mechanism (e.g. compiler-macros), and implementations offer more specific extensions mechanisms (sb-vm, ...), as well as a standard way of building portability layers across implementations. Those features are used to define and implement new standards. How is that "undeniably worse"?


You can't use compiler-macros to build cross-platform higher order abstractions, so I don't get why they come up. They're exactly as useful as any indirection facility.

So why are they relevant here?

Not that I like Python, mind you. I miss CL, but I wouldn't go back to it. My experience is that way too many of these beloved libraries have an audience of 1 or 2, sometimes a single company. If I'm going to engage with an environment like that, I'd rather do so to access the leading edge of research and compilation results like w/ Haskell. Even Racket is more desirable than CL on this scale.

EDIT: I just wanna repeat I loathe python and carry no water for that standing wave of bad design decisions. Really. Seriously. Worst.


Not sure what you count as higher order abstractions, but lparallel, fset, cl-ppcre and other libraries make use of compiler macros; this is relevant w.r.t. efficiency.


Haxl is modern. Ponylang's concurrency is modern. Rust's concurrency is modern.

lparallel and fset's affordances are not. I'm not sure why a single threaded regular expression compiler is mentioned here.

I'd have to check. Is fset even using the current standard (not the newest stuff) for immutable-friendly data structures? Last time I checked they had used a lot of older stuff from Okasaki's work and much of that has been improved upon substantially now.

Even Clojure is out of date, compared to this year's innovations!


> Which is why, in spite of all design flaws, even Go has a database interface defined on their core library.

It is useful, but Go is less standardized than CL. If we compare features that implementations provide, then we can cite LispWorks's Common SQL (http://www.lispworks.com/documentation/sql-tutorial/index.ht...).


Note that bordeaux-threads, like CLIM-SYS or CFFI-sys, is not only a portability layer, but also a standard:

    BORDEAUX-THREADS is a proposed standard for a minimal MP/Threading interface. It is similar to the CLIM-SYS threading and lock support, but for the following broad differences:

    Some behaviours are defined in additional detail: attention has been given to special variable interaction, whether and when cleanup forms are run. Some behaviours are defined in less detail: an implementation that does not support multiple threads is not required to use a new list (nil) for a lock, for example.
    Many functions which would be difficult, dangerous or inefficient to provide on some implementations have been removed. Chiefly these are functions such as thread-wait which expect for efficiency that the thread scheduler is written in Lisp and 'hookable', which can't sensibly be done if the scheduler is external to the Lisp image, or the system has more than one CPU.
    Unbalanced ACQUIRE-LOCK and RELEASE-LOCK functions have been added.
    Posix-style condition variables have been added, as it's not otherwise possible to implement them correctly using the other operations that are specified.
    Threads may be implemented using whatever applicable techniques are provided by the operating system: user-space scheduling, kernel-based LWPs or anything else that does the job.

    Some parts of this specification can also be implemented in a Lisp that does not support multiple threads. Thread creation and some thread inspection operations will not work, but the locking functions are still present (though they may do nothing) so that thread-safe code can be compiled on both multithread and single-thread implementations without need of conditionals.

    To avoid conflict with existing MP/threading interfaces in implementations, these symbols live in the BORDEAUX-THREADS package. Implementations and/or users may also make them visible or exported in other more traditionally named packages.")
And so, instead of one standard like C, you have the CL standard, as well as additional standards that are not called annexes but fulfill the same role.

From a practical point of view, I consider portability layers to be sufficient; but having those additional standards offer stronger guarantees across implementations.

https://trac.common-lisp.net/bordeaux-threads/wiki/ApiDocume...

https://common-lisp.net/project/cffi/spec/cffi-sys-spec.html...


Just for showing how Bordeaux-Threads can be considered almost universally portable. This is the list of Lisp implementations supported by Bordeaux Threads:

    - Armed Bear Common Lisp (ABCL)
    - Allegro Common Lisp (ACL)
    - CLISP
    - Clozure CL
    - CMUCL
    - Corman Lisp
    - Embeddable Common Lisp (ECL)
    - LispWorks
    - MCL
    - MKCL
    - Steel Bank Common Lisp (SBCL)
    - Scieneer CL


Looking at ECL, it doesn't seem to have a bare metal port? Or am I missing something? I take it "embedded" in this case means "links with your C code", not "runs on bare metal micro-controller."


Yes, it means that you may link it in your C code or use it standalone (hence – `Embeddable' in name). Lisp programs share ABI with C, so you are able to create library in Common Lisp which will be used from anything having FFI to C ABI.


It does, it's embedded as in "use it inside another program".


>> There are many different CL implementations out there that satisfy different use cases

And now there is also CLASP (LLVM based), great for interfacing with C++ libraries. https://github.com/drmeister/clasp


one of the perceived (by outsiders) CL drawbacks, is the eco system

if CL have a lot of libraries, it is not because CL have a large or active community, it is more because it existed for a long time

so, what is honestly the reality of CL ecosystem, is quicklisp a solid part of this ecosystem, is it easy to find and install packaged compared to perl and python for example?

do you think racket have a more reliable ecosystem?


> is quicklisp a solid part of this ecosystem,

Yes, it is very popular. You can also, without Quicklisp, download manually the project and let ASDF do the rest of steps.

> is it easy to find and install packaged compared to perl and python for example?

There is this CL environment, Portacle (The Portable CL Environment) which you just download and immediately gives you (no more steps needed):

   - SBCL lisp implementation/compiler
   - Quicklisp
   - SLIME environment
   - Customized EMACS
   - and other nice tools.
In any case, Quicklisp is easy to install as long as you have a modern ASDF version.

Quicklisp is really easy to use, as easy as, say, PIP for Python.


> Then speed is also never mentioned.

What? There's five points on the numbered list, #4 is entirely about speed.


Sorry. Speed is mentioned but overlooked, because the figures are not mentioned. It should be 2x-8x speed increase in favor of CL. For some people this truly doesn't matter (after all, Ruby is successful and loved), for other applications, it does matter a lot.


Racket is a really exciting language, especially with its focus on building small languages to solve problems.

However, where it fails for me is in its lack of interactive development. When I investigated it, there seemed to be no way to actually connect a repl to a running program.

Unlike with common lisp or clojure, with racket if you make changes to your code you have to restart the REPL, which destroys your state.

This was a big disappointment to me, because even python with ipython and autoreload allows for more interactive development.

I suspect that this decision was made because of racket's start as a teaching language, because it is simpler, but way less powerful.


> When I investigated it, there seemed to be no way to actually connect a repl to a running program.

i (poorly) implemented conditions for racket once, including the ability to drop into a repl at the point of error.

so i think you could put in place some macros that would let you get at a repl wherever you wanted. probably not the sort of thing to leave in place all the time, though

> Unlike with common lisp or clojure, with racket if you make changes to your code you have to restart the REPL, which destroys your state.

that's a different issue. i believe matthias has commented on the mailing list (some years back) that the semantics associated with doing something like that are a mess, and that's why they weren't interested in implementing it.


To me, being able to make changes to your code while keeping the current state in the REPL is key to interactive development.

My workflow is generally to build up state, and then experiment with functions on that state until I get the correct output.

This workflow is very natural in Clojure, Common Lisp and even Python (with IPython and autoreload).

However, in Racket you have to restart everything on every change. This works ok for smaller applications, but if for example, your state is a large dataset that you pull from a remote database, it becomes a little more difficult.

There are possibly workarounds, and I'm not saying that Racket is bad because of this. There are definite advantages to this approach, mainly for keeping everything simple and predictable. However, this was a roadblock for me, and the main reason why I didn't spend more time working on it.

I also miss the ability to make changes to code, refresh the browser and instantly see the changes (instead of having to restart the server after each change).

Here's a good discussion (in my opinion) of what makes a good REPL for interactive development: http://vvvvalvalval.github.io/posts/what-makes-a-good-repl.h...


It isn't in DrRacket by default but it is a common enough requested feature that they keep an example of how to add it in the documentation.

https://docs.racket-lang.org/drracket/Keyboard_Shortcuts.htm...


Thanks. Do you know if it works with the module system - for example, if I edit function definition in a module, and then send that module definition to the REPL, would any code using that module start using the new definition?


It works in geiser-mode, i don't know if it works with drracket or racket-mode.

http://docs.racket-lang.org/guide/eval.html?q=namespace#%28p...


I'm surprised no one has mentioned geiser. It sounds like exactly what you want for racket. http://nongnu.org/geiser/geiser_3.html


There is also racket-mode (available on MELPA): https://github.com/greghendershott/racket-mode


This is what I use. I find it quite comfortable / very easy to get going.


Geiser is fine. However, we can’t compare it with SLIME. The integration of the latter is much better.


I use Visual Studio Code for Racket. https://marketplace.visualstudio.com/items?itemName=karyfoun...

Before that I would use VIM with a tiled window manager that worked really well.


I believe this is a design decision, because relying on some enormous implicit repl state and updating the code in place is a good way to get yourself into states that are impossible to reach in an actual running program. Racket is a functional language and it is their belief that you should just make predictable ways to startup your interactive development than rely on the entire state of some repl image.


You're right; it's a conscious decision. I had a little discussion with Eli Barzilay and Matthew Flatt about some related matters a little while back. It was a nice discussion, but image-based or live programming is just not something they found particularly compelling.

It pretty much keeps me from using Racket, and a bunch of other things that are otherwise very nice. Given a choice, I will always choose the tools that support me in building things by modifying programs as they run. I'm just happier and more productive that way.

So one of my axes of optimization is selecting projects that enable me to work that way.


My thoughts exactly. Once you are used to this way of developing, it is really difficult to be happy with less.


Ah yes, I remember seeing an interaction that was probably yours, tried to find it for my comment, but never did.


I don't know what this refers to, but it doesn't ring a bell. The most I might have said is that the community in general is not too interested; but personally, this kind of dynamic interaction is something that I very much appreciate. In fact, when I implemented xrepl (which is now the default when you start racket) being able to modify code inside modules was one of the main goals. The only reason I didn't do something like that for Emacs is that my own use of CL/Scheme variants in Emacs was always very simple, but I actively encouraged people to do something similar for Emacs -- and Geiser/Racket-mode are two serious Emacs packages that actually do that kind of interactive use.


I asked you guys about image-saving because I was considering using Racket to build a new version of bard, and I thought I might like to both use image-saving during development and piggyback on a Racket implementation of it for use in bard's runtime. As I recall, you guys wanted to know what I found compelling about image-saving, but you didn't find it as compelling as I did.

I still might build a bard on Racket, but if so, I'll most likely build my own VM and implement my own image-saving solution for it.

Assuming, of course, that there are enough hours in the day, and enough years in a life.


I'm not sure if this is exactly what you are talking about, but with racket-mode in Emacs you can write some program, run it (C-c C-k), then add some s-expr and evaluate only that (C-x C-e) in the current context.


I use Common Lisp quite a bit, and I'm just not interested in switching to another Lisp. I've looked at most of them, and haven't seen anything compelling. CL still wins on everything that I care about (performance, portability, libraries, ease of use, books/documentation, etc.).

Even the article's list of areas where Racket is "vastly superior" is questionable, IME. Granted, the author wrote ASDF, so he has a very different perspective than I do on the module system, but in practice nothing on that list has been a problem for me, and a few of them I'd actually consider to be anti-features (like a built-in GUI library).


What domains are you using CL in? The last time I used a Lisp was inside AutoCAD and it was a lot of fun.


At work I mainly write C++, but I have a little library in CL that I've been working on for a while that can call our REST API, access and filter our log files, connect to our test infrastructure, and some other miscellaneous stuff. It started because I wanted to call our product's REST API from inside Emacs (in the Slime REPL) to make debugging and testing easier, but over time it's grown to about 1500 lines of CL to automate common tasks.

Outside of work it's my go-to language for learning new stuff. I use it for a bunch of little programming projects that I put on Github (https://github.com/jl2?tab=repositories). They're mostly just playing around with whatever topic I'm interested in at a given time. Lots of animations and (simple) computer graphics, some simple games, one or two libraries for the Raspberry Pi, etc. Nothing too exciting.

I want to start contributing to some of the open source libraries and projects (like stumpwm and sbcl) that I use a lot, but so far the few commits I've made to other projects have been really small bug fixes or trivial documentation fixes.


Do you run items in CL that return to an emacs buffer? Like say, slime-eval/run? Or are these just run in the repl itself and displayed there? Been toying with making some async stuff run via swank/slime and then display in emacs in a custom mode and buffer.


I really like this article, because it manages to be a love letter to both Racket and Common Lisp.


Interestingly, I just added a section on Gerbil, the Lisp dialect I have adopted instead of PLT, for many personal reasons.


What is your setup working with gerbil compared to emacs/slime?


My current setup does not compare favorably with Emacs/slime at this point. I use screen, I call a build script in one shell REPL, I use gerbil-mode in Emacs in the other, reload'ing things that I rebuild, or restarting the M-x run-scheme when too much has changed, copy-pasting from a gerbil-scratch.ss file, or repeatedly loading a .ss file with on-going tests. This is less than satisfactory. We hope to improve the situation this year so we can use geiser or slime or something similar.


One thing that makes racket shine is it's macro facilities. Syntax case is nice and all that, but Jesus Christ in a chicken basket I wish scheme would have standardised on syntax-parse.

Syntax case vs. syntax parse isn't and will never be close to a fair comparison. Not only is it more powerful, it also provides the users of your macros with proper error messages. It blows both unhygienic and other hygienic macro systems out of the water for anything more complex than very basic macros.


Here's the doc for the curious http://docs.racket-lang.org/syntax/stxparse-intro.html

Interesting system indeed


100% agreed. After using syntax-parse, it pains me to use anything else. It's a gem.


That Racket module functionality where you can add unit tests right with your code ("module+"), but will get stripped when compiled - that thing is quite magical. Is there another system that has this?


From thescribe's parallel comment it sounds like you're describing doctests which is a built in feature in Elixir, and I believe in Python also. Many languages have doctest libraries available, though not many support it being part of canonical documentation... I've been meaning to try the ruby-rspec doctest library https://github.com/doctest/doctest-rspec though there's several others besides that one




I don't think it is an official part of the language but the Pragmatic Programmers Elixir book does this in one of their examples code sections.


It is an official part of the language, and is automatically part of the compiled docs, which are also an official part of the language! https://elixir-lang.org/getting-started/mix-otp/docs-tests-a...


If you do work in CL for a living, may I ask what kind of applications and which area? The reason for my question is that CL (and racket) seems like a very good idea to put some time into but the market for such jobs is dead where I live (sweden). Or those jobs might be held by lispers on a lifetime ...


I don't work in CL for a living but it is my main go-to language (also at work). It is worth it to put some time into even though there's not a big market asking for CL directly.


For anyone interested in Racket, excellent book/online tutorials http://beautifulracket.com/


Anyone using Racket in the wild? (Besides the Racket team)


I believe some games companys use it. Naughty Dog of Uncharted and The Last of Us fame, being the most famous. I think they used it to create some game logic DSL or something of that sort.

More info here - https://www.reddit.com/r/Racket/comments/5g8xse/are_there_an...


I use it for a ton of pieces at work. I use R for the most part but for everything else I run Racket. I make reports with Scribble and make it into a scripting language for my file management. I also use it for making Reports and documentation with Scribble. https://plt.eecs.northwestern.edu/pkg-build/doc/scribble/ind...


Hacker News runs on Racket. :-)


wasn't HN in Arc?


And what is Arc in?


The CPython JIT/compiler is written in C. Does this mean that a Python program is then a C program?

Arc is not Racket.


I suppose the original poster could clarify whether they were interested primarily in examples of the Scheme-derivative language "Racket" being used in production, or in the system/VM/runtime "Racket" being used in production. Since Racket (the system) now makes it very easy to define new languages on top of it with the #lang keyword, it might be nice to get more clearly distinct terms for these. The way the Java world uses JVM vs. Java, e.g. you'd say that a Clojure app runs "on the JVM", but it's not in Java.


Ah, Arc, the No Man's Sky of Lisp derivatives


John Carmack is pushing for Racket at Facebook/Oculus


> trivial utilities for interactive use from the shell command-line with an "instantaneous" feel

Last time I checked CL images were huge though. Something like 24MB for a "hello world" executable, even bigger with some compilers.


With SBCL and Linux kernels with binfmt_misc support enabled (module or compiled) you can execute FASL file from command line, using the same SBCL core for all the executables and, I would add, with an "instantaneous" feel. ^__^

With Debian it should work out of the box, on other distributions it probably needs to register a wrapper/trampoline with the binfmt_misc infrastructure.

FWIK running instances don't share the same core in RAM though.

  $ cat hello-world.lisp                                                     /tmp
  (print "Hello. World!")
  $ sbcl --noinform                                                          /tmp
  * (compile-file "hello-world.lisp" :output-file "hello-world")
  ; compiling file "/tmp/hello-world.lisp" (written 23 AUG 2017 11:09:24 AM):
  ; compiling (PRINT "Hello. World!")
  ; /tmp/hello-world.fasl written
  ; compilation finished in 0:00:00.001
  #P"/tmp/hello-world.fasl"
  NIL
  NIL
  * %                                                         
  $ chmod +x ./hello-world.fasl                                              /tmp
  $ ls -lh hello-world.fasl                                                  /tmp
  -rwxr-xr-x 1 user user 330 Aug 23 11:17 hello-world.fasl*
  $ time ./hello-world.fasl                                                  /tmp

  "Hello. World!" ./hello-world.fasl  0.00s user 0.00s system 44% cpu 0.009 total
  $                                                                          /tmp
Edit for clarifications and fixing formatting


Didn't know that.


> Last time I checked CL images were huge though. Something like 24MB for a "hello world" executable, even bigger with some compilers.

You need to consider that Common Lisp includes a runtime, and this is a essential part of the goods of Common Lisp. Whenever you create a Common Lisp program, you also "embed" the runtime, and the runtime allows you to do wonderful stuff within the running code, like for example, compile code on the fly, replace function with new version (while the program is running), replace class instances with new versions of the class (while the program is running), all sorts of enforcements of the required data type for actual data* (while the program is running),

and,

it has the wonderful "conditions/restart" system. The combination of the latter with all the former, allows you to 'correct' or 'patch' a running program without really needing to recompile the whole program or even restart the running program. Famous story of usage of this feature is using it to patch a bug on the Common Lisp program for the autopilot of the NASA Deep Space One spaceship. The NASA engineers were able to debug and then fix the program while having the DS1 in space with the code running.

So, the FASL (fast load) file is basically similar to the memory file created whenever you put a laptop into "hibernation" mode; it allows you to quickly load the whole runtime, plus your code, *plus the instances of your objects or data, if needed, just straight into memory. This is how "fast load" is achieved.

Actual RAM usage of Lisp will vary with implementations; again, as I mentioned in another post, you pick the implementation that suits your particular needs.


> You need to consider that Common Lisp includes a runtime, and this is a essential part of the goods of Common Lisp. Whenever you create a Common Lisp program, you also "embed" the runtime, and the runtime allows you to do wonderful stuff within the running code, like for example, compile code on the fly, replace function with new version (while the program is running), replace class instances with new versions of the class (while the program is running), all sorts of enforcements of the required data type for actual data* (while the program is running),

Lots of other people do this without massive executables to distribute, though. CLs are inconsistent on how they can distribute base images and executables. For example, Java's compiler is a code segment you an link in if you want to carry it with you.


> though. CLs are inconsistent on how they can distribute base images and executables

There is only one Common Lisp, the 1994 ANSI Standard.

There are many implementations and they give you diverse options of delivering. For example if you want good options to package an executable, I think LispWorks has lots of options for specifically this need.


You know exactly what I meant, flavio81. Obviously.

> For example if you want good options to package an executable, I think LispWorks has lots of features.

It had 2 alternatives. I know, because I shipped some stuff to prod for Powerset on LW back in like 2009? We ultimately stopped using it precisely because we shipped less bytes more quickly with Java and Ruby.

> As for Java, to execute a Java class you need a JRE (java runtime environment), which is not small at all, so I am not sure if it gives you a big advantage.

I'm surprised to hear you say this, but in the interests of completeness I'll give you a handy way to compute the difference in bytes saved.

    (defun bytes-saved (num-deploys stdenv-size)
      (* (-1 num-deploys) stdenv-size))
A similar trivial function could help you see the amount of time saved by not compiling on site, should you respond to just ship and produce images onsite.

This requirement is even more important in the world of diverse deployment techniques that expect modularized executables as a space and performance optimization. Docker, Nim, deployments around FreeBSD jails, and even package managers have dramatically improved performance if you can offer that kind of segmentation.


You did install a JVM to run Java, didn't you? If you want to develop in Java, you need a JDK. According to this page, it amounts to 132MB:

http://www.oracle.com/technetwork/java/javase/windows-disksp...


Please see: https://news.ycombinator.com/item?id=15082806

ONE installation is inevitable. It's that very few CL-offering environments give you anything better than an image.


You are correct, but why is this a problem in the case mentioned by the author?

    $ cat > hello-world.lisp
    (defun main () (format t "Hello, World!~%"))
    $ sbcl 
    * (load "hello-world.lisp")
    T
    * (save-lisp-and-die "hello-world" :toplevel #'main :executable t)
    [undoing binding stack and other enclosing state... done]
    [defragmenting immobile space... 1110+19908+28500+22601 objects... done]
    [saving current Lisp image into hello-world:
    writing 4816 bytes from the read-only space at 0x20000000
    writing 2320 bytes from the static space at 0x20100000
    writing 2383872 bytes from the immobile space at 0x20300000
    writing 15190112 bytes from the immobile space at 0x21b00000
    writing 39911424 bytes from the dynamic space at 0x1000000000
    done]
    $ ./hello-world 
    Hello, World!
    $ ls -lh hello-world
    -rwxr-xr-x 1 user user 56M Aug 23 10:23 hello-world


Executing fasls is cool, but they will start from a new empty image, and loading fasls is actually kind of slow, so if you use lots of libraries rather than write a trivial example, that may consume hundreds of milliseconds before the program actually starts. Then again, this supposes you compiled everything into a single .fasl (which ASDF makes easy). But that will be a big fasl for each program; if you want to load from libraries, then there's also the overhead of setting up and using ASDF itself, which can also be in the hundreds of milliseconds. I can imagine many ways to improve the situation, but none are cheap (e.g. implement a background daemon to serve ready executables and/or watch the source registry for modified source; or break compatibility with the entire ASDF setup, angrying the entire community).


It's still too big, too slow [compared to gerbil] and uses a lot more memory. Gambit GC is amazingly fast, and never tenures garbage to a gen where it will collect like SBCL tends to do on many workloads. [acl/ccl/lw do not have this issue, although they are slower than sbcl]


It's not necessarily a problem, but personally for small utilities I much prefer something that supports #!-style scripts, like Guile or Racket, or that can be compiled into small, efficient executables. Let's say it's a matter of personal preference.


I wrote cl-launch so you can use #!/usr/bin/cl with lots of options and write scripts that are either portable or implementation-specific (and trivially switch from one implementation to the other).


You can use CL for #!-scripts too. SBCL has a `--script` option for running scripts. You'll probably want to create a core with whatever libraries you need for scrips, so that the start-up time will be fast. The core will be big, but it can be shared for all scripts, so that's not much of a problem.


I wrote a short introduction to a more portable Common Lisp scripting system—made by Fare, himself—at https://ebzzry.io/en/script-lisp/


If you do scripting and like Lisp, you definitely should take a look at TXR.

I wrote TXR to basically be the "acceptable {awk, perl, ruby, Python, ...}" for Lisp-minded people. Well, for me, that is; but anyone else too.


Small with CLISP. 15 years ago I developed a version control system/layer called Meta-CVS using CLISP. It compiled a custom CLISP linking set to adds some C functions, and then dropped an image (custom lispinit.mem) launched via a #! mechanism which invoked the custom CLISP executable with code linked in, and the memory image via the -M lispinit.mem option. It was small and fast enough for 2002.

I have experience deploying with Clozure on Windows. The raw memory images are large-ish, but they compress extremely well into the installer (such as NSIS). I think there is a lot of wasted space in there. Lisp images are not unlike Unix core dumps in may respects. It's similar to checkpointing a process to disk. There is a time versus speed tradeoff there; if you compress it, then you can't just map it into memory as-is.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

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

Search: