[GoLUG] I'm enjoying Guile
Barry Fishman
barry at ecubist.org
Mon Jan 20 17:52:02 EST 2025
On 2025-01-20 07:49:48 -05, Steve Litt wrote:
> So let me ask you a question: What is the optimal mindset a person
> should adopt in order to be productive in Guile?
One of the benefits of being retired is that I don't have to look at
things in that way. I don't need to adjust my mindset to program in a
predetermined language. I have programs I want to write and choose a
language that I feel makes that process the easiest. How does Guile
provide those features that fit my needs? I will answer that. I think
it fits the question you ask the best.
I really should start with what I feel are the issues first, but I will
delay that to the end of this post. First some reason to use Guile:
- Guile has all the basic Posix/Linux functions and they work the same
way as they work in C. You can do things that you do in the shell and
C. Look at and manipulate file stats, process stats, passwd file
info, rename files, cd, create directories, even fork, manipulate file
descriptors and locks, create Posix threads the same way as you expect
in C and the shell. No issues with language constraints.
Guile doesn't look like C but it has most of the functionality. It
has a Foreign Function Interface (FFI) that can bring most C APIs
into Guile, using just Guile code. There are packages for things
like PostgreSQL, SQLite, Open-GL.
Guix OS implements its whole package/process management code in Guile.
- Guile can support Object oriented programming using its built-in GOOPS
package, which is port of Common Lisp style CLOS with its meta-object
protocol. This is something that C++ doesn't have, and my frustration
with C++ in coding a very large project got me first interested in
Common Lisp.
In fact it has packages like g-golf which take the whole Gnome GTK
Object system and implements it with Guile classes, so you can do
sub-classing like you do in Vala, JavaScript, or Python.
- Guile is a mature language. It doesn't usually break your existing
code when you go to a new version. Most needed functionality is built
in, although the added packages are still dependent on changes
in their supported APIs. The only real problems I have had in the
last 20 years is the changes in Gnome2 -> Gnome3 -> Gnome4 broke
any graphical software I have written for it.
My glguile (OpenGL) program I wrote in 2005, a Guile executable with
the OpenGL interface builtin, still works, even though it uses a
obsolete approach for implementing a C level interface to Guile. I
did make some code changes in 2014 for Guile2, renaming some of some
its low level C interface functions, but no changes were needed for
updating to Guile3. Even the more massive changes in Guile1 were more
a matter of my improved understanding of Guile internals.
My changes since 2014 are in its Makefile and NixOS support code.
- Guile has a simple very syntax. And data has the same format as
source code. The core language standard is only about 50 pages
long.
Most language feature look like functions and can be easily looked
up,. On Linux this is very easy using the 'info' shell command. To
find the description of the string-index function its just:
$ info guile string-index
Its probably worthwhile learning the info command anyway, since if
you're not using Emacs, all the manuals for the GNU written commands
are available like: guile, make, gcc, and the Linux C interface like
'fprintf' are to be found there. If it doesn't have documentation it
gives you the man page.
Help using info is available by looking up the info documentation
with:
$ info info
This will tell you that you can get a list of its key bindings by
typing H.
Now the some of the things people complain about:
*** People have problems with all the parenthesis. ***
These delineate the basic blocks of the program, but keeping track of
them in reading your code can be a problem. Lisp programmers tend to
use a standard way of indenting to represent the structure of the code.
A description of that formatting can be found at:
https://mumble.net/~campbell/scheme/style.txt
After a while seeing code formatted this way, makes structuring problems
stand out. Emacs can reformat blocks of code.
But most of you use Vim. Vim does auto-indent lisp code properly. If
you position the cursor on a parenthesis, its matching
parenthesis will be also highlighted. If you press % it will move
the cursor to that parenthesis.
You can add some plugins that help.
- guile.vim
https://gitlab.com/HiPhish/guile.vim
This does syntax highlighting for Guile.
- rainbow_parenthesis.vim
https://github.com/kien/rainbow_parentheses.vim
gives each level of parenthesis a different color, so it is easier
to see matching parenthesis.
- parenedit.vim
https://github.com/vim-scripts/paredit.vim
This will do things like when you type '(' it will insert a ')' after
it and position you between them. This means that it will
try to maintain balanced parenthesis in your code so you don't
need to keep track of them. Then as you move over a closing
parenthesis you can see its balanced one highlighted, and
determine where you want to insert your new code.
*** People have problems with the lack of loop support. ***
I had this problem initially. I does have 'do', 'while', and 'for-each'
constructs. One of the first things I did was write what I thought were
the missing looping macros and then proceeded to not use them.
Mostly I use a named let, and sub-functions (as in Pascal), where rather
than automatically looping and then breaking out on your choice, you by
default break out, and only loop back when you choose to. My experience
has been this actually avoids some complex loop logic, and I find
the code easier to follow, although the loop control code isn't in one
place.
*** Now some benefits ***
Since Guile has tail-call-optimization (TCO), you don't have to worry
about nesting functions too deeply. Calling TCO functions has actually
less cost than a normal function call since it doesn't save state. And
when you finally exit its really just the normal return from the nested
function.
Guile has character sets that you can use like characters in tests
so you can (string-index line char-set:digit) to find the first
digit in a string so you can do things like:
(string-index line char-set:digit)
to find the first digit character in a string, rather than looking
for a specific character. They can be merged, intersected and
complemented.
Also some features like 'and-let*' with '(use-modules (srfi srfi-2))'
which works like a combination of and let so the first
assignment that returns #f exits the expression.
Another is the 'append-map' with '(use-modules (srfi srfi-1))'
which will look at each element in a list and produce another
list which can either filter it out or produce multiple entries.
Here is some example scheme code using this approach:
(use-modules (srfi srfi-1) (srfi srfi-2)
(ice-9 format) (ice-9 rdelim))
;;; Split name into a vector of three fields
;;; The string prior to the first sequence of digits
;;; The string sequence of digits
;;; The string after the sequence of digits
;;; It returns #f if there are no digits in the string
(define (split-out-digits name)
(and-let* ((start (string-index name char-set:digit))
(stop (string-index name
(char-set-complement char-set:digit)
start)))
(vector (substring name 0 start)
(substring name start stop)
(substring name stop))))
;;; Set of characters that are not white-space
(define char-set-nonspace (char-set-complement char-set:whitespace))
;;; Split a line into a list of words (recursively)
(define (split-line line)
(let* ((len (string-length line))
(end ;; End of first word
(or (string-index line char-set:whitespace)
len))
(next ;; beginning of next word after white space
(or (string-index line
char-set-nonspace
end)
end)))
(cond
((= end next 0) ;; Nothing on line
'())
((= end 0) ;; Leading white space (so start split afterwards)
(split-line (substring line next)))
(else ;; build list with first word and then split the rest of the line
(append (list (substring line 0 end))
(split-line (substring line next)))))))
;;; Break up a list of lines made up of words seperated by spaces into a
;;; list of words.
(define (get-words lines)
(append-map split-line lines))
;; Read a file returning a list of the lines in the file. In
;; usual Unix fashion, "-" is interpreted as taking input from stdin
(define (read-file fname)
;; Reads the contents of the open file (port)
(define (read-file-port port)
(let ((lines '()))
(do ((line (read-line port) (read-line port)))
((eof-object? line) (reverse! lines))
(set! lines (cons line lines)))))
(cond
((string=? fname "-") ;; Shorthand for standard input
(read-file-port (current-input-port)))
((access? fname R_OK) ;; Readable file exists
(let* ((port (open-input-file fname))
(lines (read-file-port port)))
(close port) ;; opened ports must be closed
lines))
(else ;; Can't read file
#f)))
--
Barry Fishman
More information about the GoLUG
mailing list