|< << >> >|

OOP with GNU Guile and GOOPS (2014/03/02)

Having worked with the Ruby programming language for many years I started to get interested in Scheme. The Scheme programming language has a small and powerful core supporting closures, hygienic macros, first-class continuations, and of course the famous interpreter functions eval and apply.

How a Common Lisp programmer views users of other languages

However one thing I don’t like about Scheme is that there are different function names for each type of arguments. E.g. adding numbers is done with +, adding lists is done with append, and adding strings is done with string-append.

(+ 2 3)
; 5
(append '(1) '(2 3))
; (1 2 3)
(string-append "a" "bc")
; "abc"

The same program would be much less verbose if + was extended to work with strings and lists, too:

(+ 2 3)
; 5
(+ '(1) '(2 3))
; (1 2 3)
(+ "a" "bc")
; "abc"

GOOPS: The Guile extension for object-oriented programming

GNU Guile It turns out that GNU Guile (the Scheme interpreter of the GNU project) has a mature extension which facilitates this. GOOPS is inspired by the Common Lisp Object System (CLOS). GOOPS not only provides polymorphic methods, it even lets you replace existing functions with polymorphic ones:

(use-modules (oop goops))
(define-method (+ (a <list>) (b <list>)) (append a b))
(define-method (+ (a <string>) (b <string>)) (string-append a b))
(+ 2 3)
; 5
(+ '(1) '(2 3))
; (1 2 3)
(+ "a" "bc")
; "abc"

The define-class method is the normal way to define a class. GOOPS requires you to define the instance attributes when defining the class. The following example defines a class <a> with attribute x and a + method. The make method is used to create the instance a of the class <a>.

(use-modules (oop goops))
(define-class <a> ()
  (x #:init-value 0 #:init-keyword #:x #:getter get-x))
(define-method (+ (u <a>) (v <a>))
  (make <a> #:x (+ (slot-ref u 'x) (slot-ref v 'x))))
<a>
; #<<class> <a> 2103c30>
(define a (make <a> #:x 123))
a
; #<<a> 22a2440>
(get-x a)
; 123
(+ a a)
; #<<a> 23713e0>
(get-x (+ a a))
; 246

Constructors and the next method

God uses Lisp

One can define a shorthand for instantiating objects. E.g. one can define a method which sets the attribute #:x to the argument multiplied by two.

(use-modules (oop goops))
(define-class <a> ()
  (x #:init-value 0 #:init-keyword #:x #:getter get-x))
(define (make-a x2) (make <a> #:x (* 2 x2)))
(define a2 (make-a 123))
; #<<a> 1689740>
(get-x a2)
; 246

IMHO it is better though to define a modified constructor directly. This is more involved but also possible.

(use-modules (oop goops))
(use-modules (ice-9 optargs))
(define-class <a> ()
  (x #:init-value 0 #:init-keyword #:x #:getter get-x))
(define-method (initialize (self <a>) initargs)
  (let-keywords initargs #f (x2)
    (next-method self (list #:x (* 2 x2)))))
(define a3 (make <a> #:x2 123))
(get-x a3)
; 246

Note the call to next-method. This essentially calls the next less specialized method for that generic function. Here is another example using an inheriting class <b> to illustrate the concept.

(use-modules (oop goops))
(define-class <a> ())
(define-class <b> (<a>))
(define-method (test (self <a>)) "a")
(define-method (test (self <b>)) (string-append (next-method) "b"))
(test (make <a>))
; "a"
(test (make <b>))
; "ab"

Metaclasses

Metaprogramming

Note that GOOPS does not implicitly create a metaclass. The following example shows how to define a metaclass <m<c>> with a class method.

(use-modules (oop goops))
(define-class <m<c>> (<class>))
(define-method (test <m<c>>) "m")
(define-class <c> ()
  (x #:init-keyword #:x #:getter get-x) #:metaclass <m<c>>)
(define o (make <c> #:x 5))
o
; #<<c> 2825d40>
(test (class-of o))
; "m"

Inspection

Inspection

One can also use GOOPS to change the way how objects get displayed and what the REPL writes to the terminal. E.g. one can define the method write for the class <a> to change the way the Guile REPL displays objects of that class.

(use-modules (oop goops))
(define-class <a> ()
  (x #:init-value 0 #:init-keyword #:x #:getter get-x))
(define a (make <a> #:x 5))
a
; #<<a> 2c64140>
(define-method (write (self <a>) port)
  (format port "(make <a> #:x ~a)" (slot-ref self 'x)))
a
; (make <a> #:x 5)

Furthermore one can implement the method display to define the way objects get displayed in formatted output.

(define-method (display (self <a>) port)
  (format port "*~a*" (slot-ref self 'x)))
(format #t "~a~&" a)
; *5*

Conclusion

I have now used GOOPS for a little while. Coming from Ruby there are a few gotchas when using GOOPS and Guile’s module system. For example one needs to use a #:re-export statement when using a module to replace the core binding for the + operator.

All in all GOOPS makes quite a mature impression and I think it makes Scheme much more amenable to developers who are used to thinking in terms of objects and classes.

To quote Guile’s foreign function interface documentation:

The more one hacks in Scheme, the more one realizes that there are actually two computational worlds: one which is warm and alive, that land of parentheses, and one cold and dead, the land of C and its ilk.

Any comments and suggestions are welcome

Further remarks

If necessary it is also possible to create objects, classes, and metaclasses dynamically. The following example instantiates the object v of class <v> of metaclass <m<v>>. Furthermore the generic test is implemented for <v>.

(use-modules (oop goops))
(define-generic test)
(define <m<v>> (make <class>
                     #:dsupers `(,<class>)
                     #:slots '()
                     #:name "<m<v>>"))
(define <v> (make <m<v>>
                  #:dsupers `(,<object>)
                  #:slots '()
                  #:name "<v>"))
(add-method! test (make <method>
                        #:specializers `(,<v>)
                        #:procedure (lambda (self) "v")))
(define v (make <v>))
v
; #<<v> 2d5a750>
(test v)
; "v"

Update:

I tweeted about the article and submitted it to Hackernews and the GUILE user list.

See also:

blog comments powered by Disqus

Latest News

2014/11/13

Graph coloring with Scheme and Graphviz
More...

2014/03/02

Why I like Object Oriented Programming using GNU Guile and GOOPS
More...

2013/09/04

Creating a calculator with Bison, Flex, and Automake
More...

2012/11/07

The choice of programming language plays a fundamental role in the implementation of machine vision systems ...
More...

2012/08/02

Building a dynamically linked C program for Android
More...

2012/07/21

Running the Racket programming language on an Android phone
More...

2012/06/10

Comparison of the Clojure programming language with Scheme/LISP
More...

2011/10/20

Can we attract good and bad things with our mind?
More...

...