[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: exposing C struct contents in Scheme

From: Robert Uhl
Subject: Re: exposing C struct contents in Scheme
Date: 23 Apr 2003 09:59:12 -0600
User-agent: Gnus/5.0808 (Gnus v5.8.8) Emacs/21.2

Pascal Haakmat <address@hidden> writes:
> I'm considering the use of guile to script my application.

Cool!  I believe that in the long run you'll find that it's very much
worth it.  Remember that emacs got where it has due to being so
scriptable in a Lisp dialect.

> I'm still not decided whether I like Scheme and/or whether it's the
> best tool for the job, but a lot of that uncertainty stems from the
> fact that I have little intuition on how to do things in the language
> or what the proper idioms are.

Well, the great thing about the Lisp family is that a) they're geared
towards functions, which is what most actual calculation is about and b)
they can do procedural programming and OOP quite easily, too.  In other
words, no matter which idiom fits your problems best, it's quite likely
that you can convince Scheme to handle it well.

> Most importantly, I am unsure how to represent/modify my application's
> struct's from guile.

Are these conceptually OOP objects?  If so, you might wish to
investigate GOOPS or some other framework to use for calculation within
guile, and simply convert to/from when calling your app's functions.

> 1. Write getter/setter functions for every property in the struct.

This is what I've done, partly because I'm writing in gtk+ and already
have to write getters & setters in C; the burden of writing them in
guile was negligible.  Even in C you might find that your library is
more robust if you always use functions (they can really be macros...)
to get/set structure members.

One bit of Scheme tradition is naming.  A value getter will typically
just be that value's name (e.g. trav-mapobject-name returns an object's
name), while the setter is the getter with a ! appended
(e.g. trav-mapobject-name! to set the name); a predicate would be a name
with a ? appended (e.g. trav-mapobject?, which returns true if its
argument is a TravMapobject).  Also, names are typically lower-case
where others might use caps.

Thus this piece of code:

(if (trav-mapobject? obj)
    (if (string=? (trav-mapobject-name obj) "Untitled")
        (trav-mapobject-name! obj
                              (string-append "Untitled "
                                                (inc untitled-count))))))

What this does is name an object in the pattern "Untitled 1" if it is
already named "Untitled."  It presupposes a macro in which takes an
argument, increments it and returns the result.  This code is not very
clean; it'd be more readable to define a function untitled-name which
would do the (string-append ...) for one--but you get the idea.

> 2. Write a generic getter/setter function that takes a string argument
>    denoting the property to get/set.

This actually wouldn't work as well as you might think.  In Scheme it's
easy to write a macro which, given a record and a string (or symbol)
representing a field, sets the field.  But at some point you'll have to
have a C interface to do the same thing (consider a function
set_field(struct foo* x, char* name, void* value)...), or you'll have to
enumerate everything anyway.  This is one of the several advantages
Scheme has over C--but it's no help with the underpinnings are C.

> 3. Use some kind of Scheme type (records? structs? objects? I'm
>    confused about these and from what little examples I have seen the
>    Scheme syntax to manipulate these types seems rather grotty).

It's really not so bad.  Generally you create a type, then you create
functions which get and set the fields of that type.  It's actually
quite elegant.  As an example:

(define foo-type (make-record-type "foo" '(name date comment)))

;; Exactly equivalent to:
;; struct foo
;; {
;;   char* name;
;;   time_t date;
;;   char* comment;
;; };
;; Except of course the Scheme members can be of any type (unless you
;; constrain them...)

(define foo-new (record-constructor foo-type '(name date comment)))

;; Equivalent to:
;; struct foo* foo_new(char* name, time_t date, char* comment)
;; {
;;   struct foo* new;
;;   new = (struct foo*)malloc(sizeof(struct foo));
;;   new->name = strcpy(name, strlen(name));
;;   new->date = date;
;;   new->comment = strcpy(comment, strlen(comment));
;;   return new;
;; }
;; Except of course that it's rather more elegant:-)

(define foo-new-blank (record-constructor foo-type))

;; Equivalent to:
;; struct foo* foo_new_blank(void)
;; {
;;   return (struct foo*)malloc(sizeof(struct foo));
;; }

(define is-foo? (record-predicate foo-type))

;; This would be handled by the compiler in C, due to the fact that C's
;; types are static.

(define foo-name (record-accessor foo-type 'name))

;; Equivalent to:
;; char* foo_get_name(struct foo* foo)
;; {
;;   return strcpy(foo->name, strlen(foo->name));
;; }
;; Except in C one has to worry about managing that copied string--and one
;; really ought to make sure that foo->name is not NULL, and one cannot do
;; anything if it's a pointer to never-never land...

(define foo-name! (record-accessor foo-type 'name))

;; Equivalent to:
;; void foo_set_name(struct foo* foo, char* name)
;; {
;;   free(foo->name);
;;   foo->name = strcpy(name, strlen(name));
;; }
;; Except one would want to check to ensure that foo->name is not NULL,
;; than name is not NULL, and one would be in trouble if name were a
;; pointer into never-never land.

You might use these functions like this:

(let ((foo (foo-new "A foo" (current-time) "My first foo")))
     (write-line (foo-name foo)) ;; displays A foo\n
     (foo-name! foo "Aristobulus Guthlacii")
     (write-line (foo-name foo)) ;; displays Aristobulus Guthlacii\n
     (write-line (is-foo? foo)) ;; displays #t\n
     (write-line (is-foo? "egg"))) ;; displays #f\n

As you can see, it's actually quite far from grotty:-)

> The third method has the drawback that it probably requires huge
> changes in my program, and I guess I'd also be relinquishing a lot of
> control, which I'm not quite prepared to do until I become more
> familiar with guile/Scheme.

That's the real argument against it.  Also, it means that to access data
members from C you have to go through the slow Scheme layer.  Nt the
best way to do things IMHO.

Anyway, I'd simply write out a full complement of getters & setters.


Robert Uhl <address@hidden>
In general, it is best to assume that the network is filled with
malevolent entities that will send in packets designed to have the
worst possible effect.                      --Jon Postel, RFC 1122

reply via email to

[Prev in Thread] Current Thread [Next in Thread]