Racket is Lisp style language. It used to be called "PLT Scheme", but it diverged from Scheme enough that they decided to rename the language.
Scheme is a fairly small language, and that's fine for teaching, but functionality Scheme standards defines is not enough for any serious programming. So every Scheme implementation adds a lot of extra functionality to their Scheme, making Scheme programs not very compatible with each other. Racket just followed this path a bit further than most, and ended up declaring itself its own language.
Racket and Clojure are the two most popular Lisp variants nowadays. In fact even though Racket isn't officially a "Scheme" anymore, most of the time when people say "Scheme", they're actually talking about Racket.
Hello, World already starts spicy:
#!/usr/bin/env racket #lang racket (display "Hello World\n")
#! line just tells the operating system to run the file with Racket.
The most interesting is
#lang racket line. Every file in Racket starts with a declaration which language it's written in with
module equivalent). Then the actual code follows.
If you're using REPL, you don't need any of that:
$ racket Welcome to Racket v8.3 [cs]. > (display "Hello World\n") Hello World > ^D
#lang can be used to provide compatibility with other Lisp variants, but it's also used for Racket to be a platform for programming language research.
There's a lot of very spicy languages available (documentation lists a few, but you can add your own). For example package
rash (Racket comes with builtin package manages, you can install it with
raco pkg install rash) is a weird Lisp-Shell hybrid:
#!/usr/bin/env racket #lang rash (define (print-upcase s) (display (string-upcase s))) ls ls *.rkt | wc -l ls *.rkt |>> print-upcase
$ ./rash.rkt hello.rkt rash.rkt 2 /USERS/TAW/100-LANGUAGES-SPEEDRUN/EPISODE-34-RACKET-SCHEME/HELLO.RKT /USERS/TAW/100-LANGUAGES-SPEEDRUN/EPISODE-34-RACKET-SCHEME/RASH.RKT
It's a weird mix of Racket code, shell commands, shell pipelines, and pipelining from shell world into Racket world.
I'll stick to
#lang racket for the rest of the episode.
#!/usr/bin/env racket #lang racket ; FizzBuzz (define (fizzbuzz n) (cond ((zero? (remainder n 15)) "FizzBuzz") ((zero? (remainder n 5)) "Buzz") ((zero? (remainder n 3)) "Fizz") (else n))) (define (fizzbuzz-loop start end) (for ([n (range start (+ 1 end))]) (display (fizzbuzz n)) (newline))) (fizzbuzz-loop 1 30)
Which prints the obvious thing:
$ ./fizzbuzz.rkt 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz
Let's go through it:
- it's parentheses everywhere, we're in a Lisp world - did you know that VSCode comes with builtin parentheses colorizer, and pretty much every editor has some kind of coloring plugins at least?
(define (function-name arguments) body)defines a function
(cond (cond1 then-code1) (cond2 then-code-2) ... (else else-code))is a way to do
if / else if / elsechain
(zero? (remainder n 15))is a way to check if
nis divisible by 15 - you could also do
(= 0 (remainder n 15))if you prefer that style
(range a b)is a range from
b-1. Seriously, why do so many languages following this ridiculous convention? The usual explanation is that it's for iterating array indexes, but nobody iterates array indexes anyway, every reasonable language has
(for ([i collection]) code)is a way to iterate over a collection -
forand related forms have a lot of different options, we use only the simple one
(newline)print the string
sand a newline character, respectively
Racket handles Unicode correctly - unlike Clojure which relies on JVM's broken Unicode support.
#!/usr/bin/env racket #lang racket (displayln (string-upcase "Żółw")) (displayln (string-downcase "Żółw")) (displayln (string-length "Żółw")) (displayln (string-length "🍰"))
$ ./unicode.rkt ŻÓŁW żółw 4 1
Racket has multiple macro systems - and then if that's not enough, then you can define your whole language and use
#lang to do it.
define-syntax-rule style macro, to implement
#!/usr/bin/env racket #lang racket (define (fizzbuzz n) (cond ((zero? (remainder n 15)) "FizzBuzz") ((zero? (remainder n 5)) "Buzz") ((zero? (remainder n 3)) "Fizz") (else n))) (define-syntax-rule (do-range var start end . body) (let ([r (range start (+ 1 end))]) (for ([var r]) . body))) (do-range n 1 30 (display (fizzbuzz n)) (newline))
. just indicates rest of the argument list. The rest should be fairly self-explanatory.
If you need other style of macros, like traditional
defmacro, it's also there (but they strongly discourage that one).
Let's fetch an HTTP GET, parse JSON, and print the results:
#!/usr/bin/env racket #lang racket (require net/url) (require json) (define url "http://worldtimeapi.org/api/timezone/Asia/Tokyo") (define response (call/input-url (string->url url) get-pure-port read-json)) (displayln (hash-ref response 'datetime))
It works as expected:
$ ./network.rkt 2021-12-27T02:19:07.009236+09:00
It's a bit more verbose than other languages (it's just
curl -s http://worldtimeapi.org/api/timezone/Asia/Tokyo | jq .datetime in shell), but for a Lisp it's not too bad.
If you want to use Lisp, your top choices are either Racket or Clojure.
They take a different approach. Clojure runs on the JVM with all the benefits and tradeoffs that entails. Racket is standalone, so you don't get access to the JVM ecosystem, but then you're spared from all the painful JVM issues.
Clojure designed the whole language from scratch so it had a lot of flexibility, but it was forced to do a lot of weird choices for sake of better JVM interoperability. Racket started with Scheme and made modest changes to it.
They both target specific niche - Clojure running Lisp on JVM, Racket providing platform for creating your own programming language. If you don't care for either of these niches, and just want a usable modern Lisp, either of these is fine.