DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 34: Racket Scheme

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")
Enter fullscreen mode Exit fullscreen mode

#! 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 / else chain
  • (zero? (remainder n 15)) is a way to check if n is divisible by 15 - you could also do (= 0 (remainder n 15)) if you prefer that style
  • (range a b) is a range from a to 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 map 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 string s 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 "🍰"))
Enter fullscreen mode Exit fullscreen mode
$ ./unicode.rkt
ŻÓŁW
żółw
4
1
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

It works as expected:

$ ./network.rkt
2021-12-27T02:19:07.009236+09:00
Enter fullscreen mode Exit fullscreen mode

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.

Code for the Racket Scheme episode is available here.

Top comments (5)

Collapse
 
epsi profile image
E.R. Nurwijayadi

Guile?

Collapse
 
taw profile image
Tomasz Wegrzanowski

Can you let me know what Guile does that's especially interesting?
From a very cursory glance it seemed like just another Scheme.

Collapse
 
epsi profile image
E.R. Nurwijayadi

I don't know the different between scheme, guile and racket.

Is that the same language? I'm curious but lost.

Thread Thread
 
taw profile image
Tomasz Wegrzanowski

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.

Thread Thread
 
epsi profile image
E.R. Nurwijayadi

O IC.

Thank you.