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!
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 #lang
(or 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
Other builtin languages
#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
Prints this:
$ ./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.
FizzBuzz
#!/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 doif / else if / else
chain -
(zero? (remainder n 15))
is a way to check ifn
is divisible by 15 - you could also do(= 0 (remainder n 15))
if you prefer that style -
(range a b)
is a range froma
tob-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 hasmap
and such. -
(for ([i collection]) code)
is a way to iterate over a collection -for
and related forms have a lot of different options, we use only the simple one -
(display s)
and(newline)
print the strings
and a newline character, respectively
Unicode
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
Macros
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.
Here's define-syntax-rule
style macro, to implement do-range
loops:
#!/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))
The mysterious .
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).
Network functions
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.
Should you use Racket?
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.
Code
All code examples for the series will be in this repository.
Top comments (5)
Guile?
Can you let me know what Guile does that's especially interesting?
From a very cursory glance it seemed like just another Scheme.
I don't know the different between scheme, guile and racket.
Is that the same language? I'm curious but lost.
Scheme is a language specification (it has a few versions, latest is version 7, or "R7RS").
Guile is one implementation of Scheme, as far as I can tell there's nothing too special about it.
Racket started as implementation of Scheme too (it was called PLT Scheme back then), but it decided that it needed so many changes they renamed the language. It's still very similar to Scheme.
O IC.
Thank you.