Hacker News new | past | comments | ask | show | jobs | submit login
Perchance to Scheme (2016) (hardmath123.github.io)
82 points by tosh on Nov 28, 2018 | hide | past | favorite | 80 comments



Racket is not academic to me. Racket equals programming fun and really is a joy to use. Academic seems to not convey the feeling one gets when using Racket. To me Racket is really the future of what languages will look like, a language to create your own DSL.

The greatest strength of Racket is its documentation. Secondly the way Racket actually teaches you how to be a better programmer. I personally grew the most due to "How to Design Programs" https://htdp.org/2018-01-06/Book/ Third the executables for cross platform is its reason why I use Racket at work. I can make it and create an executable with raco.

I feel like Racket is the most general purpose language I have ever used. I grew up on C64 Assembly, Pascal and Forth. When I learned Python I felt like I could do anything. That was until I wanted my Python code to be used on a computer that wasn't my own computer.

Python was my go to language for everything at work but I ended up moving to R about 6 years ago. I felt my R was rough around the edges and picked up Racket to learn about R's Scheme roots. It was a night and day difference. I have several pet programs that saves me hours every week.

My Racket programs are much more readible than my Python Scripts. The libraries are certainly not all there but it is actually easy and fun to create your own implementations in Racket and a library for your own use.


I used to be very much anti-Racket, until I grasped the reality that it is not a Scheme, but a Scheme-derived language that strives to combine power and rational design. I now use it daily for many pet projects, and it is great: highly effective, predictable, well-documented, and supplied with enough libraries that you can go a while in most projects before you need to implement anything outside what is core to your purpose. The ffi isn't as bad as the article suggests, either.


> Racket equals programming fun and really is a joy to use. Academic seems to not convey the feeling one gets when using Racket.

As an academic, it makes me very sad to hear these qualities, that I feel describe my work, used as the opposite of 'academic'.


I was faculty at a University and I still find the word Academic to be negative connotation and was an inaccurate statement on Racket

Define - Academic

1. c: very learned but inexperienced in practical matters 1. d: based on formal study especially at an institution of higher learning 3. a: theoretical, speculative 3. b: having no practical or useful significance

https://www.merriam-webster.com/dictionary/academic

Couldn't find a positive way to spin Academic as used by the article.


Academic as in, there are many places where the person[s] working on the code or feature abruptly stopped. As if they graduated and were no longer working on it.


That certainly isn't the case with Racket either :)


There are a wide variety of experiences to be had at the academy.


> There are a wide variety of experiences to be had at the academy.

Agreed!—but the use of the term 'academic' in this way suggests it's a universal experience. (I've never heard anyone use 'academic' to mean exciting and invigourating intellectual exploration!) I don't argue with it, as I know that's the way language goes; it just makes me sad, and, I imagine, shapes the expectations, and then to some extent even the experiences, of my students.


Alternatively - you can choose to program in Common Lisp - an industrial strength language that has been demonstrated in large projects like the QPX flight search engine (Google Flights).

For anyone who doesn't know - a big difference that Common Lisp brings to the table is that it has separate namespaces for variables and functions. This may not sound like much but it makes it much easier to write macros (code that writes code) and facilitates compile-time computing.

Another big difference between Scheme and Common Lisp is that the Common Lisp specification is _much_ larger and it comes with a much larger collection of standard built-in functions and macros and a standard object-oriented programming facility, among many other things.

I know this because I've spent the last six years implementing Clasp (github.com/clasp-developers/clasp) - a Common Lisp that interoperates with C++ and uses LLVM as the backend. There are several excellent implementations of Common Lisp - Steel Bank Common Lisp is a great place to start.


> a big difference that Common Lisp brings to the table is that it has separate namespaces for variables and functions. This may not sound like much but it makes it much easier to write macros (code that writes code) and facilitates compile-time computing.

A little more detail here: it has a lot more namespaces than that. Indeed, you can attach arbitrary different namespaces of things to symbols if you want to.

It turns out that when you're writing macros in Lisp, the majority of what you use from outside the macro are functions and the majority of symbols that you bind and use within the macro are variables, so if you put them in different namespaces, you get rid of the majority of cases where symbols don't do what you were expecting in a macro.

Now, it's not quite all of the cases, and when it does happen, it's a pain to track down. Which is why the Scheme folks spent the effort and complexity on hygienic macros (where the system rewrites all symbols internally to make variable capture impossible). At the time of Common Lisp's creation, multiple namespaces and unhygienic macros were the obvious choice for production code, since there weren't hygienic systems available. Today you can go either way. In terms of research, hygienic is the route to go, as the work over the last couple of years on strongly typed, hygienic systems has shown.


> A little more detail here: it has a lot more namespaces than that.

And I don't think the function/value split is even the most important namespacing it does with regards to macro hygiene ("functions are less likely to be shadowed" always struck me as a kind of weak argument). Symbols being namespaced under packages is a much more robust solution:

    CL-USER> (defmacro m (x) `(car ,x))
    M
    CL-USER> (defpackage :p)
    #<PACKAGE "P">
    CL-USER> (in-package :p)
    #<COMMON-LISP:PACKAGE "P">
    P> (cl:macroexpand '(cl-user::m x))
    (COMMON-LISP:CAR X)
CL:CAR isn't accessible in P, so (car x) by itself would signal UNDEFINED-FUNCTION, but the macro works correctly. Defining a P::CAR function wouldn't change anything.

That isn't just for functions, either, eg if you do:

    (defmacro awhen (pred &body body)
      `(let ((it ,pred))
         (when it ,@body)))
(where you want to leak IT), and then import AWHEN but not IT, you'll get an error that YOUR-PACKAGE::IT isn't bound to anything when you try to use it; it doesn't matter that AWHEN-PACKAGE:IT is bound.

I don't disagree with anything you said, I just think it made CL macros look flimsier than they are.


The problem is that it is quite common for cl:car to be accessible in packages, because of the practice of use-ing the cl package everywhere.

Even if I'm in my own package, I can't do this:

  (flet ((car (x) ...)))
    (m arg))
because in order to have a hassle-free Lisp coding experience in my package, I brought in all the public symbols from the common-lisp package. So my flet is in fact shadowing cl:car.

Even if I just import the specific common-lisp things I need, and car is not one of them, that could change. Today, that car above is mypkg:car. Tomorrow, someone edits the defpackage to import car (because they needed it in a function they added), and now when that file is re-read, car is cl:car.

Packages have a theoretical solution to the hygiene problem that will not be air-tight in practice due to use/importation.

Another problem is that programmers aren't going to define a large number of packages to protects parts of their program from each other. A common practice is just to make one package for an entire project.

You need fine-grained package use to achieve near perfect hygiene. Ideally, each module that provides macros should have its own package, and use only symbols from that package in the macro expansion. If module A uses a macro from package B, and that macro generates a local function F, that will be B::F, not interfering with the A::F function. If the modules are in the same master project package, the clash is not averted, obviously.


The function and variable namespace difference is a matter of taste to me. I prefer naming my variables lst to having to use #' and funcall, and the extra fuzz that hygienic macros bring is generally not a very big problem once macros become complex enough.

What CL brings over scheme is a fantastic object system. The Scheme copies are half-assed in comparison, especially considering how well-integrated CLOS is.


CL success stories are growing a bit long in the tooth. Sort through the links yourself to get an idea. http://www.lispworks.com/success-stories/index.html


The best CL implementations unfortunately are not opensource. There is still a large use of Lispworks by many heavy CL developers. And anyone who has used it knows what they have to offer over the opensource equivalents. Support is something well worth its cost. Plus in production things tend to just work on Allegro and Lispworks. They tend to bench well for the things we need. http://ftp.linux.org.uk/~ober/report.html


Hadn't heard of ITA (authors of QPX before being bought by Google) before. Now that I'm looking, it isn't clear to me QPX is still written in Common Lisp? Has anyone confirmed recently that it is still a big CL project?


I hired/partnered with one of their former engineers. QPX is most certainly still written in Common Lisp.


Thanks for the confirmation!


They also published some code: https://common-lisp.net/project/qitab/


>much easier to write macros (code that writes code) and facilitates compile-time computing

Isn't this a non-issue in scheme as it has hygienic macros?

Note: my only exposition to lisp is via elisp, so I might not know what I'm talking about.


Most CLers hate scheme macros since they almost uniformly believe that syntax-rules is the only macro facility. I myself have implemented defmacro using syntax-case because it is sometimes nice to have.

Hygiene solves the issue of namespacing macros, but at the cost of a very complex implementation. Implementing hygiene is HARD and I don't think it can even be called a solved problem. The current systems have issues that have proven to be hard to resolve.


That's intersting, because the lisp 'the right thing' approach [1] calls for correctness and ease of use over simplicity of implementation.

[1] as opposed to the alledged 'worse is better' of the unix world.


It is a matter of preference. Sometimes you end up using gensym in scheme as well, especially for introducing identifiers in bulk.


> uniformly believe that syntax-rules is the only macro facility

far from it. CLers know that Scheme has zillions of different macro systems. Which makes the whole topic highly confusing and intellectually interesting.


What I meant by that is that CLers usually complain about hygiene as if it was impossible to break. I don't know how many times I have had to explain that syntax-rules is the high level macro facility and that all schemes provide their own lower level macro facility, be it explicit/implicit renaming (chicken), syntax-case (r6rs) or syntactic closures (Chibi?).

I understand the complaint that this isn't portable. It isn't. R6RS tried to standardise it. Hopefully r7rs does better in that regard.


> that all schemes provide their own lower level macro facility and that all schemes provide their own lower level macro facility ...

or the equivalent of Lisp's DEFMACRO. There are tons of options. But it's a bit disappointing that such a central feature hasn't found acceptance of a main solution in the Scheme world. R7RS small has only syntax-rules AFAIK, and something like syntax-case is planned (?) for the R7RS large (which does not yet exist, AFAIK). It's not even clear to me whether 'syntax-case' is universally seen as 'solved problem'.

Anyone who has a basic idea of Scheme should know that syntax-rules is not the only option - a short look into a manual of an existing Scheme implementation should be sufficient...


Not quite, the low level macro facility provides hygiene as well (well, maybe not explicit renaming macros) with the ability to break hygiene when desired. These systems can be used to implement an unhygienic macro system (iirc, my defmacro about 7 lines, including the syntax case boilerplate).

R6RS standardised syntax-case, so any r6rs scheme already has it, but due to many things r6rs was a huge controversy and many implementations never started using it. Guile, chez, racket, Kawa, jscheme, larceny all support it. I expect syntax case in one form or another to make it into r7rs large, but you never know. Lots of cool things are making its way into r7rs large, including HAMT-based maps. There must of course be a vote, but that is just too good to not have :)


> Clojurescript.... Personally, I don’t like it on a matter of principle: I don’t like things that compile to JavaScript. It doesn’t seem like anything you really need to write better code.

Maybe I don't understand what the alternative is when you want to write client side code for the browser - js?? I use Clojurescript and find clear benefits.

By way of example I often found the general setTimeout functionality to be a nightmare for scheduling delays. Most of the time it works, but periodically I find it blocks or fails (via a dependancy chain) and then have to spend time arranging code abnormally to mitigate. In clojurescipt I use core async.

  (defn timeout [ms]
    (let [c (async/chan)]
      (js/setTimeout (fn [] (async/close! c)) ms)
     c))
Never looked back. Never had a delay problem - Ever.

cljs core.async and many other features have made it so much more enjoyable to write client side code. Don't even get me started on callbacks.

> Oh, and one last thing. I feel it’s obligatory at this point for me to say, please don’t spend too much time researching Scheme dialects. Just pick Racket and start coding

I've had the opposite experience so thanks, but no thanks.


What was your experience with Racket?


This dates back about 5 years ago, but I found Racket's libraries were rough. Often they amounted to prototype work that never got fleshed out, they were ill maintained, low in quality, and many just wouldn't load. And these were general purpose libraries; like even just basic database stuff.

I moved on to Clojure.


I am guilty of writing my own Scheme (R5RS) interpreter as well, and a key takeaway from the experience was that many things calling themselves Scheme interpreters are missing core parts of Scheme.

Implementing a language that resembles Scheme is quite easy. Implementing call-with-current-continuation and hygienic macros AND getting the tricky little details correct is not.[0] An analogy might be someone offering "ability to write SQL" for their fly-by-night database without stating up front that joins are not supported.

[0] Old test suite run against old Schemes: http://sisc-scheme.org/r5rs_pitfall.php


"Implementing a language that resembles Scheme is quite easy. Implementing call-with-current-continuation and hygienic macros AND getting the tricky little details correct is not."

(tangential) and this is where most "build your own compiler" style books fall down.

They teach you to implement ultra simple languages, but don't tackle memory management/garbage collection, concurrency, production grade typecheckers, and for lisplikes, macro systems, etc.


> (tangential) and this is where most "build your own compiler" style books fall down.

Have a look at http://t3x.org/s9book - it covers a Scheme implementation including GC, call/cc, low-level macros, type checking, tail call elimination, etc.


You will love Lisp in Small Pieces [0] then. :) It covers various lisps (mostly Scheme and Common Lisp) including how continuations can be implemented.

Hygienic macros are not hard to implement [1]. I can't speak to the efficiency of my implementation though.

[0] https://www.goodreads.com/book/show/1168500.LISP_in_Small_Pi...

[1] https://github.com/eatonphil/bsdscheme/blob/master/examples/...


Call/cc is pretty simple to implement depending on how you chose to represent your scheme code. Using CPS call/cc is trivial.

Hygienic macros are however never trivial. It is so many kinds of tricky, and together with the nice debugability of macros you find yourself in a nice place.


A minor update: Now Chez Scheme is open source (Apache license) https://github.com/cisco/chezscheme


Racket is now integrating the best parts of Chez Scheme, for a future release.


Racket-on-Chez will be considerably slower than vanilla Chez - Racket semantics incur some overhead. Here are some rough benchmarks from a year ago:

http://blog.racket-lang.org/2018/01/racket-on-chez-status.ht...


Other schemes that may be of use:

1) Gambit. Compiles to C. Interesting tools built on top are Termite (Erlang-like concurrency framework) and Lambdanative

2) TinyScheme - super minimalist embeddable scheme.

3) Kawa Scheme - on JVM. Much smaller and faster than Clojure when I used it last.

4) Bigloo - can compile to C & Java bytecode.

Note ChezScheme is now open source and has very good documentation.


To add to the list, PicoBit Scheme for microcontrollers, currently PIC18 and ARM, https://github.com/stamourv/picobit

There was a port of one of PicoBit predecessors, Bit Scheme, to the XMOS architecture, with realtime GC and support for the native XMOS features http://soft.vub.ac.be/~cderoove/publications/master_thesis_r...


As a guile user myself, I feel I must say that guile works just fine also when you are not embedding it. It is a very capable scheme, and even though it is not as fast as chez or racket, things have gotten a lot better (the upcoming JIT in the 3.0 release means at least a 2x speedup).


Oh,and thanks to pthreads and guile fibers the concurrency situation is probably the best of any scheme.


Does the sheer amount of cool stuff out there feel overwhelming to anyone else? I want to learn this and this and this and that and this and that thing and this other thing over there.


Because of this I can't focus on anything because learning anything feels like it has infinite opportunity cost, spirals into anxiety. My irrational response is to watch Netflix because if I were instead learning X, I would be concerned that I'm not learning Y0, ..., YN for a very large N.


I'm probably halfway through my career and still feel like a perpetual beginner because of this syndrome. It has brought me many material and intellectual pleasures and tangible benefits, including a decent living; but I increasingly feel like if I want to progress in my career, it's time to specialize a great deal in a tiny subset of my interests. There's real power in being able to say No to things, and real wisdom in knowing what to say No to. I fear I lack both of these! It's vexing in the extreme.


> ClojureScript is a compiler from Clojure to JavaScript (Clojure originally targeted Java). It is not a Scheme dialect, not does it feel like one. It’s a LISP dialect.

Hell no. The linked website, http://wiki.c2.com/?LispSchemeDifferences lists differences between Common Lisp and Scheme. Clojure is not a Common Lisp implementation.


Clojure(Script) is neither a Scheme nor an implementation of Common Lisp but is a LISP dialect. LISP dialect languages are simply a group of lisp-like language not just implementations of Common Lisp. https://en.m.wikipedia.org/wiki/List_of_Lisp-family_programm...


Scheme is generally also a LISP Dialect, more so like Clojure, since it inherits many of the LISP features directly.

Generally Clojure has very little in common with LISP dialects, besides some concepts. It has no linked lists, almost no functions are the same, almost no macros are the same, no special forms are the same, similar named operators often do something different, ... Think ATOM, CONS, CAR, CDR, APPEND, SETQ, NULL, DOLIST, DOTIMES, ... basically the original core of LISP is not present in Clojure. Literally no Clojure code runs in LISP dialects and no LISP code runs in Clojure dialects -> without completely rewriting it. Some Scheme ran in LISP - with more or less effort to do so. Nowadays that's not very common.

Clojure may in abstract terms be a LISP dialect, but the expectation that anything from LISP can be applied directly to Clojure is not the case. You can't take any LISP book and execute the examples in Clojure , nor would it make much sense.


I personally can't consider Clojure as a lisp dialect. According to McCarthy, lisp is built on top of five fundamental primitives: CONS, CAR, CDR, EQ, and ATOM. Excepting for EQ, Clojure has none of these or define them differently than the other lisp dialects, breaking the inter-compatibility of the core language.


Sure that Clojure is a dialect of Lisp, but that statement shouldn't be backed by a link to a website that describes differences between Scheme and Common Lisp. CL and Clojure are two completely different dialects and confusing them for each other is an error.


There's this new implementation called "gerbil"[1] which runs on top of gambit, is still very young but looks very promising.

It's opinionated in a good way (IMHO).

[1]. https://cons.io/


Looking at it, I'd call it a new language, not a Scheme implementation. Its semantics are much different from Scheme's.


Umm superficially you could say it is anything. The reality is it IS Scheme. (define (foo bar) (format "hi ~a" bar)) works just fine. As will any of the Scheme code, as it's merely syntactic sugar, (import :std/sugar) that you are confusing with a whole new language.


Look at things like:

    (define lst '(4 5 6))
    (set! (car lst) 3)
    (display (car lst))

    ;; 3
That's a pretty serious semantic departure. It's at least as different from RnRS as Racket, which insists it is not Scheme.


I was requesting valid scheme that did not work properly. Given the https://ecraven.github.io/r7rs-benchmarks/ shows Gerbil passing more tests than many other implementations, it seems hard to call it a "non-scheme".


There's an SRFI for generalised set! that lots of implementations (Racket, Guile, Chicken, etc) support: https://srfi.schemers.org/srfi-17/srfi-17.html


Neat. Learn something new every day.


It's not in widespread use though.


Emacs Lisp is most certainly not a Scheme.


The plan is to replace Emacs Lisp with Guile, which is a Scheme:

https://www.emacswiki.org/emacs/GuileEmacs


No, that's not the plan. The plan is to replace the Emacs Lisp runtime with a Guile-based runtime.

See here: 'is a branch of GNU Emacs that replaces Emacs’s own EmacsLisp engine with the Elisp compiler of Guile.'

The word is 'ENGINE'. That's the underlying language runtime.

It's neither GNU Emacs right now, nor will Emacs Lisp be replaced.


I thought that project is perennially under development, and at this point in time more like good thing to look at than having a real chance of getting merged with master?


Probably. ;-)


Scheme, on the other hand, is very loosely specified. This is a good thing: the core language is so small that the entire standard fits on about 50 pages...It’s not nebulous, it’s simply minimalist. This is why Scheme is more of an idea than a language.

Smalltalk wound up being quite loosely specified. This, combined with the small "core," enabling single developers rolling their own, resulted in a high degree of fragmentation of the overall language community.


I noticed that quote as well.

Odd thing to call RnRS Scheme loosely specified. The RnRS spec is extremely well-written - and it even includes a semantics.

Now "Scheme" used to mean "a Scheme like language" is of course loosely specified - but that's another story.


One important thing the author failed to mention (probably due to the publication date) is that Guile also has compiler front ends for ECMAScript and Emacs lisp (and there's a lua front end in the works). So far, Guile is my go-to scheme.

https://www.gnu.org/software/guile/


And Gambit has backends for JS, Java, Ruby, Php, C and no doubt some others.


I know this is a big controversial can of worms, but as a filthy casual who just happens to like Lisp, the idea of R6RS is pretty appealing to me. This post seems very much R5RS-oriented.

Are there any R6RS-conformant Schemes that are recommended?


Not a R6RS fan myself, so no definitive answer. But AFAIK Chez (now open source as someone else posted) is R6RS-compliant and one of the fastest implementations. Also, R7RS-small is final I think. Not sure about R7RS-large. Here's an old discsussion: https://news.ycombinator.com/item?id=11700167.


I think Chicken Scheme recently became r7rs in their new version 5


Chicken R7RS is still a work in progress AFAIK


Racket has an official R5RS and an official R6RS modes (you must start your program with #lang r6rs or with #!r6rs) and also an unofficial R7RS mode.


The author concludes with:

> Oh, and one last thing. I feel it’s obligatory at this point for me to say, please don’t spend too much time researching Scheme dialects. Just pick Racket and start coding.


I prefer Chicken Scheme, which recently just had a new release (version 5).

It actually compiles to C and then to binaries for your specific platform.

Check it out: https://www.call-cc.org/


The fact that Chicken actually has a package manager and central repository is a big plus for me.


Racket also has a package manager and central repository.


Gerbil has a package manager too.


These days Gambit/Gerbil are a much more pragmatic Scheme.


What about PicoLisp?!


what about mit/gnu scheme?




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: