Muto - Blog - Feed


Planting Flowers in Guile

Guile is a cute programming language that can do a lot of cool things!


Getting Started

Guile comes with something called "The REPL", which stands for "Read, Evaluate, Print, Loop", as in:

  • Read the user's input.
  • Evaluate that input.
  • Print the output.
  • Loop (as in "repeat the process").

Start the REPL by running guile in a terminal.

You'll be greeted with a prompt that looks like this:

scheme@(guile-user)>

Welcome to the REPL! Let's do some programming.


Types

In programming, we work with data, and there are different types of data to work with, such as numbers and letters. Types include things like:

  • "Hello!" - String (group of characters)
  • #\a - Single character.
  • 5 - Integer
  • 5.64 - Float
  • #t - Boolean (#t is true. #f false)

Math & Strings

First off, note that Spaces and tabs (known as whitespace) are ignored by Guile.

A string is a bunch of letters like "Hello". To make Guile say something, we use (display). Open the REPL and type:

(display "Field of tulips")

The parenthesis () are necessary. Once this executes, you'll see the not-so-surprising output: "Field of tulips"!

We can also do math in Guile. To add a few numbers, use a plus sign (+) and any amount of numbers afterwards:

(+ 2 3 5)
;; Anything after a semicolon is a comment.
;; Comments are ignored by Guile.

Guile can also multiply, divide and subtract:

(* 2 6)       ; Multiply 2 by 6
(/ 6 2)       ; Divide 6 by 2
(- 6 2)       ; Subtract 2 from 6
(- (+ 4 2) 6) ; Subtract 6 from 4 + 2

Making the REPL Comfy

You can add tab completion and command history to the REPL by enabling Readline! Create a file named ~/.guile (it might already exist), and put these lines at the bottom:

(use-modules (ice-9 readline))
(activate-readline)

Lists

We can make a list of things in Guile by using an apostrophe '

;; A single element starts with an apostrophe
'tulip

;; A list of elements can be grouped with '()
'(tulip rose daisy sunflower)

;; Get the FIRST element of a list with car
;; (This will return 'tulip)
(car '(tulip rose daisy sunflower))

;; Get the REST of the elements with cdr
;; (This will return '(rose daisy sunflower))
(cdr '(tulip rose daisy sunflower))

;; Mix car and cdr to find what you want!
;; (This will return 'fourth)
(car (cdr (cdr '(first second fourth fifth))))

;; You can also select sublists, like so:
;; (This will return '(b c))
(cdr (car (cdr '((a b c) x y z))))

The last few examples looks a bit awkward. Guile has a shorthand for combining car and cdr:

  • (caar lst) is the same as (car (car lst))
  • (cddr lst) is the same as (cdr (cdr lst))
  • (cadadr lst) is the same as (car (cdr (car (cdr lst))))
;; This will return 'sunflower
(cadddr '(tulip rose daisy sunflower weed))

A note on cons

(Still working on it!)


Definitions

The REPL is nice for small things, but it's not for big programs. For that we use .scm files! SCM is short for Scheme. Guile is a Scheme.

For this bit, open up a text editor. My favourite is Emacs because it's obviously the best editor for Guile! You should use it, too (okay fine, you can use whichever editor you prefer.)

Start a new file named flowers.scm. Inside the file, type the following:

(define flowers '(tulip rose daisy sunflower))

(define (replace-car x y) ; Define a new function
    (cons x (cdr y)))     ; Attach x to (cdr y)

(display (replace-car 'lily flowers))
(newline)

This code says:

  • Define a variable, flowers, that holds a list.
  • Define a function called replace-car, that puts element x in list y.
  • Run replace-car, taking lily and (cdr flowers) as its inputs.

We use display to print the output to your terminal, and newline to print a newline afterwards, per convention.

Now you can run the program by running: guile flowers.scm.

You should see the output (lily rose daisy sunflower), which is what we wanted!

A note on compilation

The first time you run the program, you may see a weird long output about "compiling". This is because Guile compiles the program before running it.

The second time you run the program, however, Guile doesn't need to compile it again; instead, it uses the compiled binary!

If you edit the file and run it again, Guile will act accordingly and re-compile the file!


Lambda

lambda allows us to create nameless functions. For example, we can write a lambda expression to square a number:

((lambda     ; Start a lambda expression
   (x)       ; List of inputs (1, in this case)
   (* x x))  ; Procedure to perform
 4)          ; Input to use.

We can also use lambda to create named functions, by pairing them with define! Start a new file called mys-quare.scm, and write:

;; Function to square a number
(define my-square
  (lambda (x)
    (* x x)))

;; A cleaner way to write my-square
(define (my-square2 x)
  (* x x))

This function takes a number and squares it. Let's try it out! Open Guile and load the file with (load "my-square.scm"). Now let's test our functions:

(my-square 3)

We used 3 for this example. You should see the desired output (in this case, 9)!


Recursion

Recursion is when a procedure "does itself" until a certain condition is met. Basically, it's a a loop.

Create a file named weeds.scm

;; Remove all occurences of "x" from list "lst"
(define (pull-weeds x lst)               ; Define a function
  (if (null? lst)                        ; If the list is empty:
    '()                                  ; Then we're done.
    (if (eq? x (car lst))                ; If x = (car lst)
      (pull-weeds x (cdr lst))           ; It's a weed. Drop it.
      (cons (car lst)                    ; Else, cons it
            (pull-weeds x (cdr lst)))))) ; Onto the recursion.

;; Define a list that we can work on:
(define garden '(rose weed sunflower weed daisy weed))

;; Call the 'pull-weeds' function on our garden:
(pull-weeds 'weed garden)

The list garden has a lot of weeds, and we want to remove them! The function does the following steps:

  • Define pull-weeds, which takes an element x and a list lst.
  • If the list is empty, we are finished.
  • Otherwise, if (car lst) is a weed
  • Perform pull-weeds on (cdr lst), dropping the current weed.
  • Else, keep the current non-weed, and attach it to the final output.

Pattern Matching

Pattern matching is a helpful method used to… Well, match patterns! We start by loading the (ice-9 match) module. It's included in the default Guile installation, so there's no need to download anything!

After the match module is loaded, we tell Guile "I want you to find needle x in haystack y!" Take this program as an example:

(use-modules (ice-9 match))

(match 3
  (1 'one)
  (2 'two)
  (3 'three)
  (4 'four))

Since the "case" we specified is 3, our output is "three"! Pattern matching makes for beautiful, readable code. This is what our file would have looked like without pattern matching:

(define (patmat a b)        ; Define a function
  (if (null? b)             ; If b is an empty list:
    '()                     ; We're done here.
    (if (eq? a (caaar b))   ; But if a = (car (car (car b)))
      (cadr (cadr b))       ; Return (car (cdr (car (cdr b))))
      (patmat a (cdr b))))) ; Else, recur on the rest of b.

(define mylist
  '(((1) one)
    ((2) two)
    ((3) three)
    ((4) four)))

(display (patmat 3 mylist))
(newline)

This "homemade" function for pattern matching is alright, but the match library is much more robust. Let's write our match program with a lambda, to give us the option to use more numbers than just 3!:

(use-modules (ice-9 match))

(define (patmat a)
  (match a
    (1 'one)
    (2 'two)
    (3 'three)
    (4 'four)
    (else 'no)))

Very nice! Lets run some tests real quick:

(patmat 2) ; => 'two
(patmat 4) ; => 'four
(patmat 8) ; => 'no

We can also have "unknown numbers" in our patterns

(use-modules (ice-9 match))

(define (add-middle-nums n)
  (match n
      ((1 x y 0) (+ x y))
      ((2 x y 8) (+ x y))
      ((5 x y 9) (+ x y))
      (else 'nope)))

Pretty cool, pretty cool!

There's a lot of cool things you can do with pattern matching, but it would be too long to explain right now. Someday maybe I'll scratch up on it a bit more!


Read & write to files

This is a work-in-progress.

You can print an entire file by displaying it, character by character, and stopping once you reach an "end-of-file" charecter:

;; 'file' is a file, 'hello.txt'.
;; "r" means "We're *reading* from this file"
(define file (open-file "hello.txt" "r"))

;; Look at the next character. If we're at
;; the end of the file, exit. Otherwise,
;; print the next character and repeat.
(define (print-file x)
  (if (eof-object? (peek-char x))
      '()
      (begin
        (display (read-char x))
        (print-file x))))

You can also write to a file, for instance:

;; "w" means "We're *writing* to this file"
(let ((out-file (open-file "hello.txt" "w")))
  (display "This text is sent to 'hello.txt'!\n" out-file))

I'll just plop a few more examples below, for reference.

;; "a" means "Append to an existing file"
(let ((out-file (open-file "hello.txt" "a")))
  (display "This text is sent to hello.txt!\n" out-file))

;; Print the next S-expression from the input file
(let ((in-file (open-file "hello.scm" "r")))
  (display (read in-file)))

;; Print the first line
(use-modules (ice-9 rdelim))

(let ((in-file (open-file "hello.txt" "r")))
  (display (read-line in-file)))

;; Print the file until the letter 'n' is found
(do ((file (open-input-file "hello.txt")))
    ((char=? (peek-char file) #\n) '())
  (display (read-char file)))

Maybe soon I'll make a little section on directory streams and whatnot…


A text adventure game!

Let's make a short, simple text adventure game. First, here's the " game.scm" file:

(use-modules (ice-9 match))

(define inventory
  '(rose lily poppy))

(display "You stand in a field of rolling hills, dark grass, and flowers.
It's drizzling softly, the cool breeze gently ruffles your hair.
 i: Leave and go inside the house.
 t: Pick tulip.
 l: Pick lilac.
 d: Pet dog.
 >")

(match (read-char)
   (#\i (display "You went inside. -The end"))
   (#\t (display "You picked a tulip!") (cons 'tulip inventory))
   (#\l (display "You picked a lilac!") (cons 'lilac inventory))
   (#\d (display "You petted a nearby dog! Good puppy!")))

#\ tells the computer "The following letter is a character, not a variable or a list.", so when you press "A" on your keyboard, Guile sees #\A.

(read-char) is a way to say "Stop the program and 'read' the next character from the user's input!"

This adventure is very short because I want the article to be very short. There are no Good ending/Bad ending's or different paths. As a word of advice: when making an adventure game, you can break it down to multiple files by using (load "filename.scm"), for instance:

(#\i (load "inside-house.scm"))

I hope this all makes sense! If it doesn't, don't worry! I don't think anyone really understands it, anyway!


Beautiful resources

Guile is an implementation of the "Scheme" language, meaning that mostly any book on Scheme will work in Guile!

I hope this helped at least a little bit! After all this talk about flowers, I really feel like wearing some flowery perfume & steeping myself a flowery tea, you can do the same if you like! Until next time, stay kind! -Muto