Skip to content

Instantly share code, notes, and snippets.

@cellularmitosis
Last active January 21, 2023 15:42
Show Gist options
  • Save cellularmitosis/b647bd77e4cc56e3c37b6d0724730246 to your computer and use it in GitHub Desktop.
Save cellularmitosis/b647bd77e4cc56e3c37b6d0724730246 to your computer and use it in GitHub Desktop.
Gist mirror of "Learn Scheme in 15 Minutes"
;; This gives an introduction to Scheme in 15 minutes
;;
;; First make sure you read this text by Peter Norvig:
;; http://norvig.com/21-days.html
;;
;; Then install GNU Guile
;; NOTE: I won't encourage using Racket but if you really want,
;; I still give you some hints in this tutorial. But the
;; tutorial will base on Guile and RnRs.
;;
;; openSUSE: zypper install guile
;; Debian: apt-get install guile-2.0 (or see your distro instructions)
;; MacOSX: Building Guile 2.0 on the Mac
;; http://irrealblog.blogspot.hk/2011/03/building-guile-2.html
;; Windows try web: http://repl.it/languages/Scheme
;;
;; More general information can be found at:
;; http://www.gnu.org/software/guile
;; Important warning:
;;
;; Going through this tutorial won't damage your computer unless
;; you get so angry that you throw it on the floor. In that case,
;; I hereby decline any responsability. Have fun!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Fire up Scheme:
;; Type 'guile' for GNU Guile
;; Or just use the browser for web version
;;
;; Now look at the prompt:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Semi-colons start comments anywhere on a line.
;;
;; Scheme programs are made of symbolic expressions (s-exps):
(+ 2 2)
;; This symbolic expression reads as "Add 2 to 2".
;; Sexps are enclosed into parentheses, possibly nested:
(+ 2 (+ 1 1))
;; A symbolic expression contains atoms or other symbolic
;; expressions. In the above examples, 1 and 2 are atoms,
;; (+ 2 (+ 1 1)) and (+ 1 1) are symbolic expressions.
(+ 3 (+ 1 2))
;; => 6
;; `set!' stores a value into a variable:
;; Please define my-name first, or you can't assign it.
(define my-name "unknown")
(set! my-name "NalaGinrut")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 1. Primitive Datatypes and Operators
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Numbers
9999999999999999999999 ; integers
#b111 ; binary => 7
#o111 ; octal => 73
#x111 ; hexadecimal => 273
3.14 ; reals
6.02e+23
1/2 ; rationals
1+2i ; complex numbers
;; Function application is written (f x y z ...)
;; where f is a function and x, y, z, ... are operands
;; If you want to create a literal list of data, use ' to stop it from
;; being evaluated
'(+ 1 2) ; => (+ 1 2)
;; Now, some arithmetic operations
(+ 1 1) ; => 2
(- 8 1) ; => 7
(* 10 2) ; => 20
(expt 2 3) ; => 8
(quotient 5 2) ; => 2
(remainder 5 2) ; => 1
(/ 35 5) ; => 7
(/ 1 3) ; => 1/3
(exact->inexact 1/3) ; => 0.3333333333333333
(+ 1+2i 2-3i) ; => 3-1i
;;; Booleans
#t ; for true
#f ; for false -- any value other than #f is true
(not #t) ; => #f
(and 0 #f (error "doesn't get here")) ; => #f
(or #f 0 (error "doesn't get here")) ; => 0
;;; Characters
;; According to RnRs, characters only have two notations:
;; #\ and #\x
;; Racket support #\u, but it's never Scheme.
#\A ; => #\A
#\λ ; => #\λ
#\x03BB ; => #\λ
;;; Strings are fixed-length array of characters.
"Hello, world!"
"Benjamin \"Bugsy\" Siegel" ; backslash is an escaping character
"Foo\tbar\x21\a\r\n" ; includes C escapes (only support hex)
;; try to print the above string
;; Printing is pretty easy
(display "I'm Guile. Nice to meet you!\n")
;; and unicode escapes
"\u004B" ; => K
;; Strings can be added too!
(string-append "Hello " "world!") ; => "Hello world!"
;; A string can be treated like a list of characters
(string-ref "Apple" 0) ; => #\A
;; format can be used to format strings:
(format #t "~a can be ~a" "strings" "formatted")
;; ==> print "strings can be formatted" on screen
(define str (format #f "~a can be ~a" "strings" "formatted"))
;; str was assigned to "strings can be formatted"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 2. Variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; You can create a variable using define
;; a variable name can use any character except: ()[]{}",'`;#|\
(define some-var 5)
some-var ; => 5
;; Accessing a previously unassigned variable is an exception
; x ; => x: undefined ...
;; Local binding: `me' is bound to "Bob" only within the (let ...)
(let ((me "Bob"))
"Alice"
me)
;; => "Bob"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. Structs and Collections
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Record Type (Skip this chapter if you're trying web version
(use-modules (srfi srfi-9))
(define-record-type dog
(make-dog name breed age)
dog?
(name dog-name)
(breed dog-breed)
(age dog-age))
(define my-pet
(make-dog "lassie" "collie" 5))
my-pet ; => #<dog>
(dog? my-pet) ; => #t
(dog-name my-pet) ; => "lassie"
;;; Pairs (immutable)
;; `cons' constructs pairs, `car' and `cdr' extract the first
;; and second elements
(cons 1 2) ; => '(1 . 2)
(car (cons 1 2)) ; => 1
(cdr (cons 1 2)) ; => 2
;;; Lists
;; Lists are linked-list data structures, made of `cons' pairs and end
;; with a '() to mark the end of the list
(cons 1 (cons 2 (cons 3 '()))) ; => '(1 2 3)
;; `list' is a convenience variadic constructor for lists
(list 1 2 3) ; => '(1 2 3)
;; and a quote can also be used for a literal list value
'(1 2 3) ; => '(1 2 3)
;; Can still use `cons' to add an item to the beginning of a list
(cons 4 '(1 2 3)) ; => '(4 1 2 3)
;; Use `append' to add lists together
(append '(1 2) '(3 4)) ; => '(1 2 3 4)
;; Lists are a very basic type, so there is a *lot* of functionality for
;; them, a few examples:
;; For Racket users:
(map add1 '(1 2 3)) ; => '(2 3 4)
;; For Guile users:
(map 1+ '(1 2 3)) ; => '(2 3 4)
;; add1 or 1+ is not a standard primitive, so it depends on implementations.
(map + '(1 2 3) '(10 20 30)) ; => '(11 22 33)
;; filter/count/take/drop are dwell in SRFI-1, so you have to load it first.
;; For Racket users:
(require srfi/1)
;; For Guile users:
(use-modules (srfi srfi-1))
(filter even? '(1 2 3 4)) ; => '(2 4)
(count even? '(1 2 3 4)) ; => 2
(take '(1 2 3 4) 2) ; => '(1 2)
(drop '(1 2 3 4) 2) ; => '(3 4)
;;; Vectors
;; Vectors are fixed-length arrays
#(1 2 3) ; => '#(1 2 3)
;; Use `vector-append' to add vectors together
;; NOTE: vector-append is in SRFI-43 which is not supported in Guile-2.0.9
;; or earlier. And it may not be added in Guile-2.0.10.
;; But it's proposed in R7RS, and there's a r7rs branch in Guile upstream.
;; If your Guile doesn't support vector-append, please skip this step.
(vector-append #(1 2 3) #(4 5 6)) ; => #(1 2 3 4 5 6)
;;; Hashes
;; Create mutable hash table
;; For GNU Guile
(define m (make-hash-table))
(hash-set! m 'a 1)
(hash-set! m 'b 2)
(hash-set! m 'c 3)
;; Retrieve a value
(hash-ref m 'a) ; => 1
;; Retrieving a non-present value is an exception
(hash-ref m 'd)
;; => #f
;; You can provide a default value for missing keys
(hash-ref m 'd 0)
;; => 0
;; Use `hash-remove' to remove keys (functional too)
(hash-remove! m 'a) ; => ((b . 2) (c . 3))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. Functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Use `lambda' to create functions.
;; A function always returns the value of its last expression
(lambda () "Hello World") ; => #<procedure>
;; Use parens to call all functions, including a lambda expression
((lambda () "Hello World")) ; => "Hello World"
((lambda (x) (+ x x)) 5) ; => 10
;; Assign a function to a var
(define hello-world (lambda () "Hello World"))
(hello-world) ; => "Hello World"
;; You can shorten this using the function definition syntatcic sugar:
(define (hello-world2) "Hello World")
(hello-world2) ; => "Hello World"
;; The () in the above is the list of arguments for the function
(define hello
(lambda (name)
(string-append "Hello " name)))
(hello "Steve") ; => "Hello Steve"
;; ... or equivalently, using a sugared definition:
(define (hello2 name)
(string-append "Hello " name))
;; You can have multi-variadic functions too, using `case-lambda'
(define hello3
(case-lambda
(() "Hello World")
((name) (string-append "Hello " name))))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"
;; ... or specify optional arguments with a default value expression
(define* (hello4 #:key (name "World"))
(string-append "Hello " name))
;; Functions can pack extra arguments up in a list
(define (count-args . args)
(format #t "You passed ~a args: ~a" (length args) args))
(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"
;; ... or with the unsugared `lambda' form:
(define count-args2
(lambda args
(format #t "You passed ~a args: ~a" (length args) args)))
;; You can mix regular and packed arguments
(define (hello-count name . args)
(format #t "Hello ~a, you passed ~a extra args" name (length args)))
(hello-count "Finn" 1 2 3)
; => "Hello Finn, you passed 3 extra args"
;; ... unsugared:
(define hello-count2
(lambda (name . args)
(format #t "Hello ~a, you passed ~a extra args" name (length args))))
;; And with keywords
;; the keywords are those like this #:its-name, sometimes you may see
;; :its-name without '#' in certain Scheme implementation.
;; NOTE: keywords is not in any Scheme standards like RnRs.
;; But mainstream Scheme implementation often contains it.
;; This truth also means that different implementation may has
;; different result.
;; The code below will follow Guile situation.
(define* (hello #:key (name "World") (greeting "Hello") . args)
(format #t "~a ~a, ~a extra args~%" greeting name (length args)))
;; 'define*' is similar with 'define', but you may use it for defining
;; optional args or using keyword to specify the value to specified argument.
(hello) ; => "Hello World, 0 extra args"
(hello 1 2 3) ; => "Hello World, 3 extra args"
(hello #:greeting "Hi") ; => "Hi World, 2 extra args"
;; NOTE: In Guile, all the keyword-value pairs are countered as the rest args.
;; It means 'args' here will be (#:greeting "Hi"), so its length is 2.
;; If you are in Racket, it's different, so the result will be 0.
;; Because it's not defined in RnRs, so it's implementation specific.
(hello #:name "Finn" #:greeting "Hey") ; => "Hey Finn, 4 extra args"
(hello 1 2 3 #:greeting "Hi" #:name "Finn" 4 5 6) ; => "Hi Finn, 10 extra args"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 4. Equality
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; for numbers use `='
(= 3 3.0) ; => #t
(= 2 1) ; => #f
;; for characters use `char=?'
(char=? #\c #\c) ; => #t
;; for object identity use `eq?'
;; WARNING: don't use `eq?' on numbers and characters, the reason is very simple:
;; because RnRs treat this rule as undefined!
;;(eq? 3 3) ; => Wrong! it's undefined! So it depends on implementation!
;; No matter if you got #t from above, it's a wrong usage!!!
;; Should use (= 3 3) or (eqv? 3 3)
(eq? (list 3) (list 3)) ; => #f
;; Why it's #f? Because comparison between objects depends on their head-pointers.
;; These two lists are different objects, and they have different head-pointers.
(eq? 'a 'a) ; => #t
;; Symbols are the typical objects to compare with their head-pointers
;; eqv?
;; The difference between `eqv?' and `eq':
;; You can compare numbers and characters with `eqv?'
;; When you use `eqv?' for any objects other than numbers&characters, it's the
;; same with `eq?'.
(eqv? 3 3) ; => #t
(eqv? #\c #\c) ; => #t
(eqv? 'a 'a) ; => #t
;; for collections use `equal?'
;; `equal?' will compare all the values in a collections type like record or list.
;; You can compare any objects with `equal?' safely, but inefficiency.
(equal? (list 'a 'b) (list 'a 'b)) ; => #t
(equal? (list 'a 'b) (list 'b 'a)) ; => #f
;; Choosing proper equal pred for you code, is one of the art in Scheme programming!
(eqv? "abcd" "abcd") ; => unspecified
;; Obviously, strings are collections type, so...it's your EXECISE now!
;; Another EXECISE, what's the proper 'equal-pred' for functions?
(what-to-use? (lambda (x) (1+ x)) (lambda (x) (1+ x)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 5. Control Flow
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Conditionals
(if #t ; test expression
"this is true" ; then expression
"this is false") ; else expression
; => "this is true"
;; In conditionals, all non-#f values are treated as true
(member 'Groucho '(Harpo Groucho Zeppo)) ; => '(Groucho Zeppo)
(if (member 'Groucho '(Harpo Groucho Zeppo))
'yep
'nope)
; => 'yep
;; `cond' chains a series of tests to select a result
(cond ((> 2 2) (error "wrong!"))
((< 2 2) (error "wrong again!"))
(else 'ok)) ; => 'ok
;;; Pattern Matching
;; For Racket users:
(require racket/match) ; use match module
(define (fizzbuzz? n)
(match (list (remainder n 3) (remainder n 5))
((list 0 0) 'fizzbuzz)
((list 0 _) 'fizz)
((list _ 0) 'buzz)
(else #f)))
;; For Guile users:
(use-modules (ice-9 match)) ; use match module
(define (fizzbuzz? n)
(match (list (remainder n 3) (remainder n 5))
((0 0) 'fizzbuzz)
((0 _) 'fizz)
((_ 0) 'buzz)
(else #f)))
(fizzbuzz? 15) ; => 'fizzbuzz
(fizzbuzz? 37) ; => #f
;;; Loops
;; Looping can be done through (tail-) recursion
(define (lp i)
(when (< i 10)
(format #t "i=~a\n" i)
(lp (1+ i))))
(lp 5) ; => i=5, i=6, ...
;; Similarly, with a named let
(let lp ((i 0))
(when (< i 10)
(format #t "i=~a\n" i)
(lp (1+ i)))) ; => i=0, i=1, ...
;; how to get a range? just like range(0, 9)?
;; the original 'iota' only accept one para
(iota 10) ; ==> (0 1 2 3 4 5 6 7 8 9)
;; the 'iota' in srfi-1 was extended
(use-modules (srfi srfi-1))
;; #<procedure iota (count #:optional start step)>
(iota 5 10) ; => (10 11 12 13 14)
;; means from 10 count 5 times, each step +1 (plus one is default)
(iota 5 10 2) ; => (10 12 14 16 18)
;; from 10 count 5 times, each step +2
;; If you need a Python like range(5, 10) ==> (5 6 7 8 9), try:
(define (range from to) (map (lambda (x) (+ from x)) (iota (- to from))))
;; EXECISE: you may find this 'range' implementation is not so good,
;; please optimize it if you can.
(range 5 10) ; => (5 6 7 8 9)
;; how to do iteration?
(for-each display '(1 2 3 4 5))
;; => 12345
(for-each (lambda (i) (format #t "i=~a\n" i))
(iota 10)) ; => i=0, i=1, ...
(for-each (lambda (i) (format #t "i=~a\n" i))
(range 5 10)) ; => i=5, i=6, ...
;;; Iteration Over Other Sequences
;; `for' allows iteration over many other kinds of sequences:
;; lists, vectors, strings, sets, hash tables, etc...
(for-each display '(l i s t))
;; => list
(define vector-for-each (@ (rnrs) vector-for-each))
;; export vector-for-each from rnrs only
(vector-for-each display #(v e c t o r))
;; => vector
(string-for-each display "string")
;; => string
;;; More Iterations
(do ((i 10 (1+ i)) (j '(x y z) (cdr j)))
((null? j)) ; if j is '(), just end the loop
(format #t "~a:~a " i (car j)))
; => 0:x 1:y 2:z
;;; Exceptions
;; To catch exceptions, use the 'catch' form
(catch 'my-error
(lambda () (throw 'my-error))
(lambda e (display "oh~my error!\n")))
; => oh~my error!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 6. Mutation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Use `set!' to assign a new value to an existing variable
(define n 5)
(set! n (1+ n))
n ; => 6
;; Use fluid for explicitly mutable values
(define n* (make-fluid 5))
(fluid-set! n* (1+ (fluid-ref n*)))
(fluid-ref n*) ; => 6
;; Many Guile datatypes are immutable (pairs, lists, etc), some come in
;; both mutable and immutable flavors (strings, vectors, hash tables,
;; etc...)
;; Use `vector' or `make-vector' to create mutable vectors
(define vec (vector 2 2 3 4))
(define wall (make-vector 100 'bottle-of-beer))
;; Use vector-set! to update a slot
(vector-set! vec 0 1)
(vector-set! wall 99 'down)
vec ; => #(1 2 3 4)
;; Create an empty mutable hash table and manipulate it
(define m3 (make-hash-table))
(hash-set! m3 'a 1)
(hash-set! m3 'b 2)
(hash-set! m3 'c 3)
(hash-ref m3 'a) ; => 1
(hash-ref m3 'd 0) ; => 0
(hash-remove! m3 'a)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 7. Modules
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Modules let you organize code into multiple files and reusable
;; libraries; Make sure put all the module code in one file, since
;; the modules split as files. And the module name should be same
;; with the filename, say, module named (my-cake) is 'my-cake.scm',
;; and module named (mods my-cake) is 'mods/my-cake.scm',
;; (mods submods my-cake) ==> 'mods/submods/my-cake.scm'.
;; ---begin my-cake.scm---
(define-module (my-cake) ; define a `cake' module based on racket/base
#:use-module (ice-9 format) ; the pre-requisition of current module
#:export (print-cake)) ; function exported by the module
(define (show fmt n ch) ; internal function
(format #t fmt (make-string n ch))
(newline))
(define (print-cake n)
(show " ~a " n #\.)
(show " .-~a-. " n #\|)
(show " | ~a | " n #\space)
(show "---~a---" n #\-))
;; --end my-cake.scm---
;; Be sure that the path of 'my-cake.scm' is in your current
;; %load-path list. Use `use-modules' to get all `provide'd names
;; from a module.
(use-modules (my-cake)) ; the ' is for a local submodule
(print-cake 3)
; (show "~a" 1 #\A) ; => error, `show' was not exported
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 8. Classes and Objects
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 9. Macros
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Macros let you extend the syntax of the language
;; Let's add a while loop
(define-syntax-rule (while condition body ...)
(let loop ()
(when condition
body ...
(loop))))
(let ((i 0))
(while (< i 10)
(display i)
(set! i (1+ i))))
;; Macros are hygienic, you cannot clobber existing variables!
(define-syntax-rule (swap! x y) ; -! is idomatic for mutation
(let ((tmp x))
(set! x y)
(set! y tmp)))
(define tmp 1)
(define a 2)
(define b 3)
(swap! a b)
(format #t "tmp = ~a; a = ~a; b = ~a\n" tmp a b) ; tmp is unaffected
;; But they are still code transformations, for example:
(define-syntax-rule (bad-while condition body ...)
(when condition
body ...
(bad-while condition body ...)))
;; this macro is broken: it generates infinite code, if you try to use
;; it, the compiler will get in an infinite loop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment