1. 34
    1. 7

      I was curious about Racket’s performance, so I tested it myself. I get about 48 seconds for the program on the default Racket backend, and about 25 seconds on the Chez Scheme backend. If I change the program to use #lang racket instead of r6rs (which is a quite simple change, you have to use when instead of if in one place, use a couple different list function names, and use define-struct instead of define-record-type) then it goes down to 16 seconds for the default backend and 11 seconds for the Chez backend.

      1. 1

        That’s… very impressive, actually. I’m surprised #lang r6rs brings that much overhead with it. Thank you for that.

        1. 2

          The difference is mostly that in r6rs pairs are mutable, and Racket’s mutable pair datatype is separate from (and slower than) the regular pair type.

    2. 3

      You already have the code in R7RS, so you should be able to check gerbil’s R7RS implementation.

      1. 1

        I did give Gerbil’s R7RS a try, but I didn’t know how much mentioning it would add to the article. The things I noticed:

        • It was faster than CHICKEN, but not as fast as Chez or SBCL. Considering samth’s comment about Racket’s performance, I wouldn’t be surprised if Gerbil without R7RS-compatibility would be close to Chez in terms of speed.
        • The executable was the smallest of the bunch.
        1. 1

          For speed, did you put Gambit’s (declare (not safe) (flonum) (standard-bindings) ...)? I don’t think performance would change with or without R7RS.

          For executable size, did you statically link in the Gambit VM? Or maybe you don’t even need it?

          This, http://www.iro.umontreal.ca/~gambit/Gambit-inside-out.pdf, would be useful if you decide to go with gerbil.

          1. 1

            I compiled with -exe -static -O -prelude "(declare (not safe))", so yes, I did statically link the Gambit VM. The dynamically linked version is tiny. I will try with (flonum) and (standard-bindings).

    3. 3

      I highly enjoyed reading this for the tour of Lisps, and the conclusion that Racket might be a unifying force among Schemers. I’m interested in hearing the author explain a bit more on their thoughts about Racket.

    4. 2

      Before I go into the source code

      No proper booleans. Like in C, anything that is not nil (NULL), the empty list, is considered to be a truthy value.

      This is incorrect. CL has booleans, it is just not idiomatic to use them. Instead one uses generalized booleans

      CL-USER> (typep t 'boolean)
      T
      CL-USER> (typep nil 'boolean)
      T
      CL-USER> (typep 1 'boolean)
      NIL
      

      I am a little disappointed that the (coerce pixels ’list) is necessary. Vectors are proper sequences in Common Lisp, but {} only works on lists.

      Yes, that is something I wish CL would do different as well. Another place you encounter this is in the :collect clause of the loop macro, it only works with cons cells. If one wants to incrementally build a vector One has to wrap the loop with a call to coerce. Iterate solves that, although I just live with (coerce (loop …) ’vector)

      Sadly, that’s where the niceties end. t is a typical name for the variable in a parametric equation, but it’s also the name of the canonical “true” value in CL, so you can’t use it as the name of a parameter.

      If one used packages one would be able shadow the t symbol refer to the boolean with a package prefix, cl:t. I would just use another name for the variable but the option is there if one wants to do it anyway.

      Another difference in naming convention is that Common Lisp hackers, for some reason, avoid using -> to denote conversions

      The reason is because the standard doesn’t use ->, ej. char-code not char->code. Yes it is unfortunate, -> is more clear. Some people use it anyway.

      Finally the benchmark is including the time that it takes to do IO, I wouldn’t do that.


      From the code one can see the author mostly writes scheme. Although they made an effort to use format and loop, the code is not written in an idiomatic manner. Mostly minor details. Not using a package. Using defvar instead of defparameter for what are effectively program constants. Not using earmuffs for dynamically scope variables. Naming the predicate some-p instead of somep. The whole option implementation at the start of the code. Running the code when loading the file as opposed to wrapping it in a function. This are all minor details. There are a couple things that are important though:

      • If they cared about performance they wouldn’t have used structures to represent vectors, instead they would have used vectors! They could have used a library like https://github.com/cbaggers/rtg-math. They said quicklisp was fair game after all.

      • Unlike the scheme code, they are using single-floats in CL, I doubt they want to do that. The numbers should be written as 1.0d0 to specify double floats or one can change the value of *read-default-format*

      Still, I would like to thank the the author for taking the time and effort to write a small raytracer in several languages to compare them. It certainly took more effort to write that this comment.

      1. 2

        This is incorrect. CL has booleans, it is just not idiomatic to use them.

        I raise you a counterexample:

        * (typep t 'symbol)
        T
        * (typep nil 'list)
        T
        

        The sentence following “[n]o proper booleans” is my definition of what makes an implementation of a boolean data-type “improper”, which rehashes what you’ve linked about generalized booleans. I suppose it is a bit objective, but this is meant to be a largely objective article anyway.

        Aside from that, thank you for the comments on how the implementation could be more idiomatic. I’ll go back and adjust the specific things you pointed out for anyone looking at the comparison, and update my rather hand-wavy benchmark.

        1. 1

          The sentence following “[n]o proper booleans” is my definition of what makes an implementation of a boolean data-type “improper”

          Like in C, anything that is not nil (NULL), the empty list, is considered to be a truthy value.

          I’m not clear on what that definition is if the CL definition doesn’t match it. The type boolean is an exhaustive set of two values, (deftype boolean () '(member t nil)). Why does it matter that said members are also members of other types?

          You could have a strict variant of if that would only accept booleans in the test-form. The fact that other language constructors operate on generalized booleans is unrelated to having booleans as a proper type.

          Then again I may not be clear as to what distinction you are drawing as I don’t see the value(🥁) of booleans. In the context of application development, Generalized booleans are better for any use case I can think of. Even in typed languages like SML, I would avoid booleans in favor of enumerations. They are ok as the return value of predicates, but that’s it. People misuse booleans all the time, ej. use them as a field in their model. But that is unrelated to the purpose of your article.

          1. 1

            I’m not clear on what that definition is if the CL definition doesn’t match it. The type boolean is an exhaustive set of two values, (deftype boolean () ’(member t nil)). Why does it matter that said members are also members of other types?

            It doesn’t really matter. It’s a triviality, but nonetheless a difference between Common Lisp and Scheme. I wanted to outline differences.

            You could have a strict variant of if that would only accept booleans in the test-form. The fact that other language constructors operate on generalized booleans is unrelated to having booleans as a proper type.

            Then I suppose my comment relates to language constructs operating on generalized booleans, rather than the existence of a boolean type. Do you feel I should be more explicit about that?

            Then again I may not be clear as to what distinction you are drawing as I don’t see the value(🥁) of booleans. In the context of application development, Generalized booleans are better for any use case I can think of. Even in typed languages like SML, I would avoid booleans in favor of enumerations. They are ok as the return value of predicates, but that’s it. People misuse booleans all the time, ej. use them as a field in their model. But that is unrelated to the purpose of your article.

            I’m not necessarily defending Scheme’s approach to booleans. I agree with you 100%.

            1. 1

              It’s a triviality

              I wanted to outline differences.

              Do you feel I should be more explicit about that?

              Like you said, it is not central to the article. If the goal is to outline the differences I would only mention that nil is both false and the empty list. I would focus in another big difference that you don’t mention, ‘modules’ (in the broadest sense of the word). From what I know of Racket, modules in Scheme are tied to files. While the mechanism in CL is packages, which is also a factor in the need for macro ‘hygiene’.

              But that is mostly beside the point. The article is already good as it stands. It has taken you at least a couple of weekends to write and it was more enjoyable that the low-effort medium rant about. So again thanks for taking the time to do so.

              1. 1

                But that is mostly beside the point. The article is already good as it stands. It has taken you at least a couple of weekends to write and it was more enjoyable that the low-effort medium rant about. So again thanks for taking the time to do so.

                Even if rant-y, I think I got quite a bit out of this. Thank you.

                I’ve just pushed the changes you suggested (with the exception of using bagger’s library, I’ll try this tomorrow).

    5. 2

      Also, function definitions have to come in order, or your CL compiler is going to yell at you. This is enough to drive me up the wall.

      Not sure what you mean here, as that’s certainly not true. It’s also a bit interesting that the specific name conflicts you ran into are counted as a negative since that’s something that’ll happen in any language. Good write-up overall, though.

      1. 2

        Not sure what you mean here, as that’s certainly not true.

        You’re right, I need to correct that. That’s SLIME’s behavior when you C-c C-c, but neither SBCL’s when you `compile-file’ nor SLIME’s when you use the proper C-c C-k for compiling a file. I knew that felt wrong. Thanks for pointing that out.

        It’s also a bit interesting that the specific name conflicts you ran into are counted as a negative since that’s something that’ll happen in any language.

        It didn’t happen in Scheme, and it didn’t happen in Fennel. My point there was more that ‘t’ is an unfortunate name for that constant, as it’s a commonly used variable name in mathematics.

      2. 1

        When I first read this I thought that “yell at you” referred to CL’s annoying habit of DISPLAYING IDENTIFIERS IN ALL CAPS but upon re-reading it I think it’s actually about hoisting?

    6. 1

      Not sure if the author is reading here, but FWIW, PI is defined as a constant in CL.

      1. 1

        Yes, I was remarking that it is defined as a constant in CL, while in Scheme it is not.

    7. 1

      I’ve been unknowingly using Emacs Lisp since much earlier – not in the sense of writing packages – my old man taught me how to use Emacs when I was nine

      (This makes me jealous)

      I decided to scrap my BMP encoder and go with PPM instead. Netpbm is text-based

      I was playing around with a NetPPM encoder myself in Scheme, just a few weeks ago, having have read the same article, and storage-wise, it’s helpful to remember that NetPPM can also be generated using binary data. The header stays the same, but after the P6 100 200 256 part, you just write bytes.

      Clojure’s license makes it a complete non starter for me, sorry. Live free or die.

      What’s this about? I recently read RMS saying that an argument to not consider Clojure a proper lisp is that it’s (allegedly, idk) based on cons-cells, so that “the most basic programming techniques of Lisp don’t work in Clojure”.

      1. 6

        What’s this about?

        EPLv1 isn’t GPL-compatible. Not totally evil, but it’s enough for me to want to avoid using it.

        https://www.eclipse.org/legal/eplfaq.php#GPLCOMPATIBLE

        1. 3

          Fun facts:

          • the EPLv2 is GPL-compatible
          • the Clojure developers have collected copyright assignment from all contributors, which would be needed to update the license to use it
          • despite this, the Clojure developers have indicated that they are uninterested in doing so
        2. 1

          Would it make you feel better or worse if it were MIT?

          1. 2

            If Clojure were licensed under MIT? I would feel better, as it’s compatible with the GPL.

      2. 3

        If you’re interested in Clojure, by the way, Phil Hagelberg (who wrote Leiningen, the standard Clojure build tool) is nowadays contributing a lot to Fennel (which is also mentioned in the article, and compiles down to Lua).

        Libre Lounge has a really fascinating interview with him that covers that topic – caveat auditor, I ended up buying his Atreus keyboard after listening to the podcast.

        1. 5

          Careful, for he lurks among us. Some say that if you say the word “technomancy” out loud three times he may appear.

      3. 2

        From what I can see, Clojure uses the Eclipse Public License? (based on https://github.com/clojure/clojure).

        Is the EPL that problematic?