Basic questions and answers for GNU Guile (from the perspective of a Pythonista)

Guile is a Scheme interpreter which is the canonical extension mechanism for GNU programs. It aims at providing good access to C-libraries and (what’s the most interesting part for me) it can use real OS-level threads (no global interpreter lock).

Here I want to share some of the answers I found when looking into GNU Guile to expand my programming horizon.

The source of this document is available from py2guile.

Table of Contents

Motivation

When reading The White Belt in the book Apprenticeship Patterns I felt a pang of recognition.

You are struggling to learn new things and it seems somehow harder
than it was before to acquire new skills. The pace of your
self-education seems to be slowing down despite your best efforts. You
fear that your personal development may have stalled.

I have grown so familiar with Python that the challenges I face at my PhD no longer require me to dig deeper with it. Actually I mostly just recreate solutions I already used for something else. So I decided to take a leap and learn something completely different. My basic options were Scheme and Fortran: Scheme because it offers new options in structuring programs, Fortran because it would be useful for my job. I chose Scheme, because it provides its own sense of elegance while Fortran would be a more concrete skill but it would not challenge my current notions of programming.1

Using the Scheme implementation from GNU was a natural choice, then.

(though I was not happy with the many parens. See wisp: Whitespace to Lisp for my take on that)

Solutions

Creating Games

Range of numbers: iota

(iota 6) ; 0 1 2 3 4 5
(use-modules (srfi srfi-1))
(iota 6 1 0.1) ; 1.0 1.1 1.2 1.3 1.4 1.5

Performance test

To get the runtime of some code, just use ,time on the REPL (the guile shell)

To really profile the code, you can use ,profile on the REPL.

If it says “no samples recorded”, you may need to run the code multiple times in a loop to give the profiler enough data to provide a solid report.

As example, type the following into the REPL:

(define (mycode i) (display i)(newline))
,profile (mycode 1)
,profile (let loop ((i 10000)) (cond ((zero? i) i) (else (mycode i) (loop (1- i)))))

Testing Framework

Running tests from docstrings

Nothing yet for running tests directly from docstrings. But you can extract docstrings:

(define (foo) 
  "bar" 
  "baz")
(procedure-documentation foo) ; ⇒ "bar"

This only works, if the docstring is NOT the only form in the function. You have to have at least one other statement, otherwise you just get #f. If the docstring is the only data, it is by definition the return value of the function.

(define (foo)
  "bar")
(procedure-documentation foo) ; #f

You can find a discussion about this on the guile-devel mailinglist.

To discover all bindings which could have docstrings, you can use something like the code in this example (from a hint on IRC):

(module-map (lambda (sym var) sym) (resolve-interface '(ice-9 vlist)))

Help at the REPL (guile console/interpreter)

To get information about a given function, you can use the ,d meta-command. Example:

,d append
,describe append

Alternatively you can use the help function from the texinfo reflection module:

(use-modules (texinfo reflection))
(help append)

To enable this systemwide, just add the module import to your ~/.guile

(use-modules (texinfo reflection))

To get all local variables, you can use ,bindings (,b). Example:

(define a 1)
,b
,bindings

this returns

scheme@(guile-user)> ,b
a                       #<variable e70f60 value: 1>
%module-public-interface #<variable 895a60 value: #<interface (guile-user) 882bd0>>

To get a list of all variables from the module guile, use

,in (guile) ,b

To get a list of all imported modules, use ,import or ,use without arguments.

,import
,use

You can find more of these REPL-commands via ,help all:

,help all

The easiest way, however, is activating readline and just hitting tab twice:

echo "(use-modules (ice-9 readline))(activate-readline)" >> ~/.guile

Essentially this:

(module-map (λ (sym var) sym) (resolve-interface '(guile)))

and

,use ; returns (guile-user)
,in (guile-user) ,use ; returns module listing
,in (guile) ,b ; returns the bindings
,in ...

Essentially this:

(map (λ (x) (cons (module-name x) (module-map (λ (sym var) sym) (resolve-interface (module-name x))))) (module-uses (resolve-module '(guile-user))))

To get a dir as in Python, you can use this:

(import (ice-9 optargs))
(import (oop goops))
(use-modules (texinfo reflection))

; define basic dir
(define* (dir #:key (all? #f))
  (if all?
      (map (λ (x) (cons (module-name x)
                        (module-map (λ (sym var) sym) (resolve-interface (module-name x)))))
           (module-uses (current-module)))
      (module-map (λ (sym var) sym) (current-module))))
; add support for giving the module as argument
(define-generic dir)
(define-method (dir (all? <boolean>)) (dir #:all? all?))
(define-method (dir (m <list>)) (module-map (λ (sym var) sym) (resolve-interface m)))
; add support for using modules directly (interfaces are also modules, so this catches both)
(define-method (dir (m <module>)) (module-map (λ (sym var) sym) m))

You can use it like this:

(dir) ; all local bindings, excluding imported modules
(dir #t) ; all available bindings, including imported modules
(dir #:all? #t) ; same as above
(dir '(ice-9 optargs)) ; all exported bindings from the (ice-9 optargs) module
; => (let-optional* define* let-keywords let-keywords* define*-public defmacro* defmacro*-public let-optional lambda*)
(dir (resolve-module '(ice-9 optargs)) ; all bindings in the module
; => (let-optional* parse-lambda-case %module-public-interface let-keywords let-keywords* define*-public defmacro* defmacro*-public *uninitialized* let-optional vars&inits)

Publish a project cross-platform easily

No simple solution yet.

Where’s the standard library? What does ice-9 mean?

Different from Python, GNU Guile clearly separates its own standard library from other sources of packages. To get Guile-specific functionality, check the modules under ice-9. According to the Guile Status, the name ice-9 is a play on the fictional alternative state of water described in Kurt Vonnegut's novel Cat's Cradle from 1963. That water crystallizes at room-temperature and turns all water it touches into crystals.

With time, perhaps this exposure can reverse itself, whereby programs can run under Guile instead of vice versa, eventually resulting in the Emacsification of the entire GNU system. Indeed, this is the reason for the naming of the many Guile modules that live in the ice-9 namespace, a nod to the fictional substance in Kurt Vonnegut's novel, Cat's Cradle, capable of acting as a seed crystal to crystallize the mass of software.

An ice-crystal to create more crystals in an environment which would normally melt the ice looks like a fitting name for the core of the GNU Ubiquious Intelligent Language for Extensions, intended to give free software an edge in a proprietary world.

Optionals and Keyword-Arguments

Keyword-Arguments are somewhat harder to use in Guile Scheme than in Python. Here’s an example in Python:

def hello(who="World"):
    print "Hello", who + "!"
hello() # => Hello World!
hello("Friend") # => Hello Friend!
hello(who="Friend") # => Hello Friend!

The same in Guile would look like this:

use-modules : ice-9 format
define* : hello #:optional (who "World")
          format #t "Hello ~A!\n" who
hello ; => Hello World!
hello "Friend" ; => Hello Friend!
define* : hello #:key (who "World")
          format #t "Hello ~A!\n" who
hello #:who "Friend" ; => Hello Friend!

I actually need two different definitons to support optional and keyword arguments.

Not having keyword arguments double as optionals looks strange from the perspective of a Pythonista, but I think using this approach avoids errors due to accidentally passing a keyword-argument when you passed one more argument to a function - as it sometimes happened to me when working with methods in Python. For some notes on keyword-arguments in Guile, have a look at optionals, keywords, oh my! from Andy Wingo.

Python just treats keyword arguments and optionals the same and even allows reusing mandatory arguments as keyword arguments, which makes it really nice to use functions, for which the developer omitted keyword-arguments, consistently with functions using keyword-arguments. But on the other hand, keyword-arguments increase the coupling between functions: I cannot rename function arguments without changing every function-call which used keyword-arguments for calling. Due to this, I am not certain whether Python actually strikes the right balance here or whether Guile Scheme found the better solution.

If you want to provide keyword-arguments without default-value (like switches), then code in Guile Scheme is the one which looks easier:

def keywords_dict(**rest):
    print rest
def keywords_none(me=None, you=None):
    if me:
        print "me"
    if you:
        print "you"

In Guile Scheme this looks like the following:

define* : keywords_rest . rest
          display rest
define* : keywords #:key me you
          when me
               display "me\n"
          when you
               display "you\n"

Note, though, that rest in Python is a dictionary, while rest in Guile Scheme is a list which you still need to parse yourself. So with Guile Scheme I would always provide an explicit list of keyword-arguments. And it is not explicit from the definition that the keywords will be #f in Guile Scheme when I do not pass them.

Personally I love keyword arguments, because they make it possible to understand how a function will see an argument from the call to the function itself. From my current perspective, Guile falls a bit short of Python here, though it is still pretty solid. And it actually provides some features beyond those in Python: It allows keyword-arguments to refer back to optional arguments or earlier keyword-arguments:

define*
    hello
      . #:optional (me #f) 
      . #:key (who "World") (end (if me "???" "!"))
    format #t "Hello ~A~A\n" who end
hello ; => Hello World!
hello #:who "Friend" ; => Hello Friend!
hello #t ; => Hello World???
hello #t #:who "Friend" ; => Hello Friend???
hello #:who "Friend" #:end "!!!" ; => Hello Friend!!!

You can define the default value of an argument based on a previously defined argument. And I remember quite a few instances where I hacked together addon-code which roughly did the same in Python.

Questions and Answers

How do I enable readline support?

Put the following into ~/.guile

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

Short form:

echo "(use-modules (ice-9 readline))(activate-readline)" >> ~/.guile

How to get a list of all currently available functions and macros in the interactive shell?

just hit TAB twice in an empty line. Alternative: Enter ( and then hit TAB twice.

Requires active readline.

How to import just one symbol from a module?

(define local-name (@ (category module) symbol))

Why not add strings with +: prefix-notation is different!

Regular scheme does not allow adding strings with +, but from my experience with python that is a very intuitive way of working with strings - and I think it contributes to making string-manipulation easier with python.

But guile offers a way to do so by using GOOPS, the Guile Object Oriented Programming System:

(use-modules (oop goops))
(define-method (+ (x <string>) (y <string>) . e) 
  (if (equal? '() e) 
      (string-append x y) 
      ; else
      (apply + (append (list (string-append x y)) e)) ))
; and the error case
(define-method (+ (x <string>) (y <number>)) 
  (throw 'mixed-strings-and-numbers-exception 
         (format "+ cannot be used with mixed type input: x ~a y ~a" x y)))

And this gives you the full python-behaviour.

But as Mark Weaver states, this is actually a problem for systems using prefix notation, because different from python scheme allows for an empty call to +:

(+) ; this returns 0
(apply + '()) ; also returns 0, but here you might not know whether
              ; the list is empty, because you might get it from
              ; somewhere else!

And GOOPS changes + globally, so that every bit of code I use would suddenly see a changed (+), which might break existing error-handling:

(let ((foo 1)(bar "2"))
  (catch 'wrong-type-arg 
    (lambda () (+ foo bar)) ; checked
    (lambda (key . args) ; handler for wrong types
      (format #f "foo or bar is not a number!"))))

And this actually applies to all code running in the same process:

<Arne```> mark_weaver: I have a question to be really, really sure (as in “I’m
          certain that I can misunderstand anything if it even has a minuscle
          amount of ambiguity”): When I add a method with GOOPS, does that
          affect libraries I use? When I release a module which adds a method
          with GOOPS, and someone uses that module, does that affect other
          modules he or she uses?  [09:41]
<mark_weaver> when you add a method to any generic function (such as +) with
              GOOPS, you are effectively mutating that generic function.
              Anything thing else in the system that uses that generic
              function is affected, yes.  [09:43]
<mark_weaver> in this case, if you add string concatenation to +, that will
              affect + for all users of + throughout the guile process.

And this is due to the added flexibility we get from prefix-notation:

<mark_weaver> Using + for that is likely to lead to bugs, where you do
              something like (apply + <list>) which works just fine as long as
              <list> has at least one element, otherwise it breaks.  [05:16]
<mark_weaver> note that this issue doesn't arise with infix languages, because
              theres no way to use infix '+' with zero arguments.  [05:19]

Later, Mark noted two things which are better ideas, I think:

<mark_weaver> now, list concatenation (append), string concatenation
              (string-append), and vector concatenation *are* conceptually the
              same thing.  combining all of those things into a ++ operator
              would be much more defensible.  [00:39]
<mark_weaver> although you'd still have a problem of what to return when
              passed zero arguments.  [00:40]
<mark_weaver> that reminds me of another place where there'd be a conflict
              with combining these two concepts into one: one might reasonably
              decide to extend numerical addition to vectors and lists, by
              adding successive elements together, e.g. (lambda (x y) (map + x
              y))  [00:42]

So, why not just adjust append to also append strings, and + to apply to lists of lists and do vector addition?

++ looks pretty dangerous for me: Intuitively I would have thought that it does vector addition…

<mark_weaver> Arne`: fair enough.  If you keep those two concepts separate, I
              have no further complaints.  I'll just note that (append)
              returns '().

Module scoped generics

Andy Wingo noted another option of overloading + which does not have global implications:

<wingo> you can make new, scoped generics
<wingo> module-scoped anyway  [10:17]
<wingo> make a new binding (perhaps by making a pure module, with the binding
        for + renamed to guile:+), and call guile:+ as the + method with
        unspecialized args  [10:22]
<Arne```> wingo: I don’t think I really understand yet… So I would rename + to
          guile:+ and then define a private function named +, which I do not
          export?  [10:26]
<wingo> you would export +
<wingo> or alternately you define "add", and export it as "+"
<wingo> probably easier that way  [10:27]
<Arne```> but since it is not the same object as the standard +, goops would
          not affect libraries which do not use the module?
<wingo> correct
<Arne```> Nice!
<nalaginrut> ah nice  [10:29]
<Arne```> Now we’re getting to the kind of magic I like: The explicit kind :)

This looks pretty safe, so I might switch to that instead of the hack I have above.

Specialized functions (object orientation)

Why does this work?

(use-modules (oop goops)) (define-method (+ (x <string>) (y <string>)) (string-append x y))

While this does not?

(use-modules (oop goops)) (define (add x y) (+ x y)) (define-method (add (x <string>) (y <string>)) (string-append x y))

Answer: + is a core function, which is already defined in a way which allows specializing (generic function). But if you use (define), you create a standard scheme function without goops support. You need to use (define-method) from the start:

(use-modules (oop goops)) (define-method (add x y) (+ x y)) (define-method (add (x <string>) (y <string>)) (string-append x y))

How do I print stuff to the terminal?

There are display for nice-looking output, write for exact output and format for building output messages.

display

display shows strings with linebreaks, but without a linebreak at the end:

display "foo\nfoo\n"
; gives
; foo
; foo

write

write shows data-structures in a way which can be put into the guile-interpreter (the REPL) within (quote) and give the same datastructure again. Also without linebreak.

write "foo
foo
"
; gives
; "foo\nfoo\n"
write '(foo (bar))
; gives
; (foo (bar))

format

Format allows using strings with replacements. It either prints to stdout or returns a string.

format #t "foo ~A\n" 'bar
; writes
; foo bar
; with a linebreak at the end
format #f "foo ~A\n" 'bar
; returns the string "foo bar\n"
format #f "foo ~A\n" '(foo bar "moo")
; returns the string "foo (foo bar moo)\n"
format #t "foo ~A\n" '(foo bar "moo")
; writes
; foo (foo bar moo)
; to stdout with a linebreak at the end.

Glossary

Terms used by Schemers.

  • Thunk: One block of code. Enclosed by parens.
  • Procedure: Function.
  • Body: All the forms in a procedure.
  • Form: Something which appears in a body: A definition or an expression.
  • Definition: A form which starts with (define.
  • Expression: Any form which can appear in the function body which is not a definition. See R4RS for the clear definition.

Footnotes:

1

Actually I started with Fortran for practical reasons, but found myself drawn back to scheme all the time. So I decided to go with my natural drive and dive into scheme.

Author: Arne Babenhauserheide

Created: 2016-06-26 So 13:41

Emacs 24.5.1 (Org mode 8.2.6)

Validate