“Programming is a joy. That’s why people do it. No one should spend hours in front of a computer terminal out of some dreary sense of duty, or because they have some vague notion of becoming “computer literate”. That’s not the point. Programming ought to be fun—and if you’re not having fun, you shouldn’t waste your time.”
—Michael Eisenberg, “Programming in Scheme” (1988)
I go through phases of playing with Scheme for utility code, maybe even portable dev; while FreePascal is a better language for this, I’m frustrated by the lack of library support and the useless iOS situation.
Scheme’s always been an emergency backup language; it was fun to learn back in the ’80s and early ’90s, and both SICP and TSPL are good books, but nobody wanted to pay for Scheme dev, and anyway the language is very annoying to write. I often treat it as a logic puzzle to get anything done, not a useful tool. But it does have good library support, and it can compile to very fast binaries, despite having GC pauses and consuming 2x as much memory as a C program. Maybe I can get better at solving problems in it, build up some libraries, and make it useful?
So the current landscape is:
- Chez Scheme:
- Very fast to compile and at runtime, competitive with C compilers.
- Great interactive REPL, not just a half-broken readline history like pretty much every other Scheme.
- Debugger is reasonably good, and integrated in the REPL. All I really use myself is (trace FOO) and (inspect BAR), but non-caveman coders will make better use of it.
- Current R6RS implementation plus extensive chezscheme library.
- By R. Kent Dybvig, author of TSPL, and Cisco currently employs him to maintain Chez Scheme.
- REPL environment is called a café, which I find charming. Yes, I also liked all the coffee puns and iconography from early Java programming.
- Not as widely supported by tools & documentation as Racket.
- Very nice GUI.
- Current R6RS implementation plus extensive racket library.
- Built around making multiple languages; I don’t really care about this. I loathe “Typed Racket”, one of the worst combinations of ideas in history.
- Tons of documentation.
- Mediocre performance. There’s a project to rehost Racket on Chez Scheme, which would fix this, but then why use Racket?
- Doing anything in the GUI destroys your environment, all the objects you’ve made, unlike any LISP or Scheme ever. So it’s utterly fucking useless as an interactive REPL. I can’t say enough bad things about this. ★☆☆☆☆ Kill On Sight.
- Compiles to C and thence to native binaries, with nice FFI to C libraries.
- Mostly old R5RS, with a few extension libraries.
- Terrible REPL, only really usable as a compiled language.
- Scheme R7RS benchmarks
Chez Scheme is the clear winner for me; if I was a novice, I might choose Racket and not realize that the REPL is a broken abomination for a while. If I was only doing C interop, Chicken would be better.
Editing in BBEdit works OK, but it doesn’t know how to find function definitions. I guess Vim has current syntax, but I’m kinda over that habit unless I have to sysadmin. I have never been emacsulated and never will.
Atom’s symbols list doesn’t do any better. But if you do want to use it, install package language-racket (all other language-schemes are R5RS at best), and then add some file types to config.cson:
"*": core: customFileTypes: "source.racket": [ "scm" "ss" ]
In any editor, any language, I use hard tabs (1 char = 1 logical indentation level, obviously), and normally tabstop at 8 chars which discourages very long nesting and encourages me to extract functions. Scheme is indentation hell, so set the tabstop to 4 spaces. (The code blocks below won’t show that.)
Do not criticize my C-like paren/brace placement; I prefer clear readability of code structure to some obsolete Emacs dogma.
So, let’s see it work, with hello.ss:
#!/usr/bin/env scheme-script (import (chezscheme)) (format #t "Cheers ? , ~a!~%" (car (command-line-arguments))) (exit)
% chmod 755 hello.ss % ./hello.ss Mark Cheers ? , Mark!
Now for something more serious:
;; stdlib.ss ;; Copyright © 2015,2018 by Mark Damon Hughes. All Rights Reserved. (library (stdlib) (export inc! dec! currentTimeMillis randomize input atoi) (import (chezscheme)) ;; Variables (define-syntax inc! (syntax-rules () ((_ x) (begin (set! x (+ x 1)) x)) ((_ x n) (begin (set! x (+ x n)) x)) ) ) (define-syntax dec! (syntax-rules () ((_ x) (inc! x -1)) ((_ x n) (inc! x (- n))) ) ) ;; Date-Time (define (currentTimeMillis) (let [(now (current-time))] (+ (* (time-second now) 1000) (div0 (time-nanosecond now) 1000000)) ) ) ;; Random Numbers ;; "Anyone who attempts to generate random numbers by deterministic means is, of course, living in a state of sin." —John von Neumann (define (randomize) (random-seed (bitwise-and (currentTimeMillis) #xffffffff) ) ) ;; Input/Output ;; Reads a line from stdin, ends program on EOF (define (input) (let [(s (get-line (current-input-port)) )] (if (eof-object? s) [begin (display "Bye!\n") (exit) ] s ) ) ) ;; Strings ;; Converts a string to an integer, 0 if invalid (define (atoi s) (let [(n (string->number s))] (if (eqv? n #f) 0 (inexact->exact (truncate n)) ) ) ) )
#!/usr/bin/env scheme-script ;; guess.ss ;; Copyright © 2015,2018 by Mark Damon Hughes. All Rights Reserved. (import (chezscheme)) (import (stdlib)) (define (guess) (display "I'm thinking of a number from 1 to 100, try to guess it!\n") (let [(theNumber (+ (random 100) 1))] (define guesses 1) (do [(break #f)] (break) (format #t "Guess #~a? " guesses) (let [(g (atoi (input)))] (cond [(or (<= g 0) (>= g 100)) (display "Try a number from 1 to 100.\n") ] [(< g theNumber) (display "Too low!\n") (inc! guesses) ] [(> g theNumber) (display "Too high!\n") (inc! guesses) ] [else (display "You got it!\n") (set! break #t) ] ) ) ) ) (display "***GAME OVER***\n") ) (randomize) (guess) (exit)
chez-compile.zsh, with my thanks to Graham Watt for explaining wpo and libraries:
#!/bin/zsh if [ $# -ne 1 ]; then echo "Usage: chez-compile.zsh MAINNAME" exit 1 fi rm -f *.so rm -f *.wpo mkdir -p bin cat <<ENDTEXT |scheme -q --optimize-level 3 (compile-imported-libraries #t) (generate-wpo-files #t) (compile-program "$1.ss") (compile-whole-program "$1.wpo" "bin/$1") ENDTEXT rm -f *.so rm -f *.wpo if [ -f "bin/$1" ]; then chmod 755 "bin/$1" fi
Now I just:
% chez-compile.zsh guess compiling guess.ss with output to guess.so compiling stdlib.ss with output to stdlib.so ((stdlib)) () % bin/guess I'm thinking of a number from 1 to 100, try to guess it! Guess #1? 50 Too low! Guess #2? ^DBye!
1 REM GUESS. COPYRIGHT (C) 1980,2018 BY MARK DAMON HUGHES. ALL RIGHTS RESERVED. 5 RANDOMIZE INT(TIMER()):FOR I=1 TO 10:A=RND(1):NEXT I:REM CHIPMUNK'S RANDOMIZE SUCKS 10 N=INT(100*RND(1))+1:T=1 20 PRINT "I'M THINKING OF A NUMBER FROM 1 TO 100, TRY TO GUESS IT!" 100 PRINT "GUESS #";T;"? ";:INPUT "",G 110 IF G<=0 OR G>=100 OR G<>INT(G) THEN 200 120 IF G<N THEN 210 130 IF G>N THEN 220 140 GOTO 230 200 PRINT "TRY A NUMBER FROM 1 TO 100.":GOTO 100 210 PRINT "TOO LOW!":T=T+1:GOTO 100 220 PRINT "TOO HIGH!":T=T+1:GOTO 100 230 PRINT "YOU GOT IT!":PRINT "*** GAME OVER ***" 240 EXIT:REM CHIPMUNK
But now I can think about more complex problems in Chez Scheme!
Here’s the tiniest piece of what I’ve been thinking about next: