UP | HOME

Small snippets of Guile Scheme

There’s a lot of implicit knowledge among Guile developers. Here I gather some useful snippets I found along the way.


PDF (drucken)

More useful stuff to get things done in Guile is available in guile-basics and py2guile.

log-expression: print variable name and value

During debugging I often want to display variables by name and value. I used to print name and value by hand, but this quickly becomes tedious:

(define foo 'bar)
(format #t "foo: ~a\n" foo)
;; or
(display 'foo)(display foo)(newline)

Therefore I build me something simpler:

(define-syntax-rule (log-exprs exp ...)
  (begin (format #t "~a: ~S\n" (quote exp) exp) ...))

Now I can simply log variables like this:

(define foo 'bar)
(log-exprs foo)
;; => foo: bar
(define bar 'baz)
(log-exprs foo bar (list "hello"))
;; foo: bar
;; bar: baz
;; (list hello): ("hello")

Use Guile-yaml to search for the first match in a yaml file

This uses guile-libyaml to parse the yaml file and (ice-9 match) to recursively search within the file.

The example file is demo1.yml from the guile-yaml repo:

--- 
 doe: "a deer, a female deer"
 ray: "a drop of golden sun"
 pi: 3.14159
 xmas: true
 french-hens: 3
 calling-birds: 
   - huey
   - dewey
   - louie
   - fred
 xmas-fifth-day: 
   calling-birds: four
   french-hens: 3
   golden-rings: 5
   partridges: 
     count: 1
     location: "a pear tree"
   turtle-doves: two

I’m searching for the first match to "partridges":

(import (yaml) (ice-9 match))
(define demo (read-yaml-file "demo1.yml"))
(let match-demo ((demo demo))
  (match demo
    ((("partridges" . b) c ...) b)
    (else (if (pair? demo)
              (or (match-demo (cdr demo)) (match-demo (car demo)))
              #f))))
;; => (("count" . "1") ("location" . "a pear tree"))

To use this snippet, start guile as guile -L . in the guile-libyaml repo.

Count occurences of each key in an alist

This was asked by fnstudio in the #guile channel on Freenode.

Given a list of pairs (like xy-coordinates), count how often the first element appears.

(define xydata '((1 . 1)(1 . 2)(1 . 3)(2 . 1)(2 . 2)))

(define (assoc-increment! key alist)
  "Increment the value of the key in the alist, set it to 1 if it does not exist."
  (define res (assoc key alist))
  (assoc-set! alist key (or (and=> (and=> res cdr) 1+) 1)))

(fold assoc-increment! '() (map car xydata))
;; => ((2 . 2) (1 . 3))

Common Substrings and Somewhat Cheap String Similarity

Getting the longest common substring is a traditional task, a simplification of actual edit distance like the Levenshtein distance (which actually has fast estimators).

But maybe you want all common substrings without duplicates.

The following code is not optimized, but it works.

Get all common substrings

  • Usage

    Different from the requirements in in Rosetta Code, this returns not the longest common substring, but all non-consecutive substrings.

    (longest-common-substrings "thisisatest" "testing123testing")
    ;; => ("test" "i")
    (longest-common-substrings "thisisatestrun" "thisisatestunseen")
    ;; => ("thisisatest" "un")
    
  • Implementation

    Warning: This is NOT fast. It takes a few seconds when run over two text documents with around 2000 characters each.

    (import (srfi srfi-1))
    (define (longest-common-substrings s1 s2)
      (define c1 (string->list s1))
      (define c2 (string->list s2))
      (define (common-prefix a b)
        (let loop ((prefix '()) (a a) (b b))
          (cond ((or (null? a) (null? b)) (reverse! prefix))
                ((not (equal? (car a) (car b))) (reverse! prefix))
                (else (loop (cons (car a) prefix) (cdr a) (cdr b))))))
      (define (longer a b)
        (if (> (length a) (length b))
            a b))
      (define (common-substrings a b)
        (define substrings '())
        (let loop ((a2 a) (b b) (longest '()))
          (let ((prefix (common-prefix a2 b)))
            (let ((anew (drop a2 (length prefix))))
              (when (not (null? prefix))
                (let ((str (apply string prefix)))
                  (when (not (member str substrings))
                    (set! substrings (cons str substrings)))))
              (cond ((null? b) #t) ;; done
                    ((null? anew)
                     (loop a (cdr b) '()))
                    ((null? prefix)
                     (loop (cdr anew) b longest))
                    (else
                     (loop (cdr anew) b (longer prefix longest)))))))
        substrings)
      (let ((substrings (common-substrings c1 c2)))
        (define (contained-in-any-other s)
          (any identity
               (map (λ (s2) (and (not (equal? s s2)) (string-contains s2 s)))
                    substrings)))
        (reverse! (remove contained-in-any-other substrings))))
    

Somewhat Cheap String Similarity

  • Usage
    (cheap-similarity "thisisatest" "thisisatest")
    ;; => 3.0454545454545454
    (cheap-similarity "thisisatest" "thisisatestrun")
    ;; => 2.68
    (cheap-similarity "thisisatest" "testing123testing")
    ;; => 1.2142857142857142
    (cheap-similarity "thisisatest" "criecriecriecrie")
    ;; => 0.4444444444444444
    
  • Implementation

    Warning: This is NOT well defined and is mostly untested, so it might have ugly unforseed edge-cases and might give bad results for large classes of problems. It seems to do what I want (get the similarity of filenames for ordering streams by similarity for the guile media site), and it was interesting no write, but that’t about it. Use with caution!

    (import (srfi srfi-1))
    (define (all-common-substrings s1 s2)
      (define c1 (string->list s1))
      (define c2 (string->list s2))
      (define (common-prefix a b)
        (let loop ((prefix '()) (a a) (b b))
          (cond ((or (null? a) (null? b)) (reverse! prefix))
                ((not (equal? (car a) (car b))) (reverse! prefix))
                (else (loop (cons (car a) prefix) (cdr a) (cdr b))))))
      (define (longer a b)
        (if (> (length a) (length b))
            a b))
      (define (common-substrings a b)
        (define substrings '())
        (let loop ((a2 a) (b b) (longest '()))
          (let ((prefix (common-prefix a2 b)))
            (let ((anew (drop a2 (length prefix))))
              (when (not (null? prefix))
                (let ((str (apply string prefix)))
                  (set! substrings (cons str substrings))))
              (cond ((null? b) #t) ;; done
                    ((null? anew)
                     (loop a (cdr b) '()))
                    ((null? prefix)
                     (loop (cdr anew) b longest))
                    (else
                     (loop (cdr anew) b (longer prefix longest)))))))
        substrings)
      (let ((substrings (common-substrings c1 c2)))
        (define (contained-in-any-other s)
          (any identity 
               (map (λ (s2) (and (not (equal? s s2)) (string-contains s2 s))) 
                    substrings)))
        substrings))
    
    (define (cheap-similarity s1 s2)
      (define common-length
        (apply + (map string-length (all-common-substrings s1 s2))))
      (define total-length (+ (string-length s1) (string-length s2)))
      (/ common-length total-length 1.))
    

pivot a table

(apply map list '((1 2) (1 3)))
((1 1) (2 3))

Explanation: Apply moves map list into the list, so this changes to

(map list '(1 2) (1 3))

Map takes the elements from each of the arguments to call the function, so the result is

(list (list 1 1) (list 2 3))

Or in shorthand notation:

'((1 1) (2 3))

[2023-07-05 Mi]

Script with minimal startup time

To create a script that starts fast, you’ll want to avoid the parsing time.

If you have a script in a file named hello-world.scm, use the following:

#!/usr/bin/env bash
exec -a "$0" guile -L $(dirname $(realpath "$0")) \
                   -C $(dirname $(realpath "$0")) \
                   -e '(hello-world)' -c '' "${@}"
# terminate the inline comment "started" with the #! line: !#
(define-module (hello-world)
  #:export (main))

(define (main args)
  (format #t "Hello ~a\n" (cdr args)))

Then make it executable and run it as script:

chmod +x hello-world.scm
./hello-world.scm World
# => Hello (World)

As a benchmark, run it 100 times:

# exclude single-time auto-compilation
./hello-world.scm World >/dev/null
{ time for i in {1..100}; do 
./hello-world.scm World > /dev/null
done } 2>&1

I get:

real	0m1,601s
user	0m0,944s
sys	0m0,721s

So on my machine, startup time is 16ms.

[2024-01-11 Do]

Shell scripts in Guile

Similar to the script above, but with a bit more convenience.

You can write shell scripts in Guile, but you’ll need to make sure you to do it in a way which has low startup times.

The fastest I found is shell-indirection calling your scripts as modules:

====== scriptname.scm ======
#!/usr/bin/env bash
# -*- scheme -*-
# set Guile if unset
if [ -z ${GUILE+x} ]; then
        GUILE=guile
fi

exec -a "$0" "${GUILE}" -L $(dirname $(realpath "$0")) -e '(scriptname)' -c '' "$@"
; !#

(define-module (scriptname) ;; same as filename!
   #:export (main))

(define (main args)
   (display args)
   (newline))

Copy this into ~/.local/bin/, make it executable with chmod +x ~/.local/bin/scriptname.scm, and you have powerful scripting with low startup timme.

time for i in {1..100}; do scriptname.scm >/dev/null ; done
real    0m3,889s
user    0m3,172s
sys     0m1,508s

40ms runtime. Not as fast as perl or bash, but pretty good.

The difference to the 16ms in the previous example is likely due to having different system load and a bigger GUILE_LOAD_COMPILED_PATH.

[2024-04-18 Do]

define-typed

If you want to add typechecks, you can follow the format by sph-sc, a Scheme to C compiler. It declares types after the function definition like this:

(define (hello typed-world) (string? string?)
  typed-world)

That’s simple enough that a plain, hygienic syntax-rule can support it:

(import (srfi :11 let-values))
(define-syntax-rule (define-typed (procname args ...) (ret? types ...) body ...)
  (begin
    (define* (procname args ...)
      ;; create a sub-procedure to run after typecheck
      (define (helper)
        body ...)
      ;; use a typecheck prefix for the arguments   
      (map (λ (type? argument)
             (let ((is-keyword? (and (keyword? type?)
                                     (keyword? argument))))
               (when (and is-keyword? (not (equal? type? argument)))
                 (error "Keywords in arguments and types are not equal ~a ~a"
                   type? argument))
               (unless (or is-keyword? (type? argument))
                 (error "type error ~a ~a" type? argument))))
           (list types ...) (list args ...))
      ;; get the result
      (let-values ((res (helper)))
        ;; typecheck the result
        (unless (apply ret? res)
          (error "type error: return value ~a does not match ~a"
                 res ret?))
        ;; return the result
        (apply values res)))
    (unless (equal? (length (quote (args ...))) (length (quote (types ...))))
      (error "argument error: argument list ~a and type list ~a have different size"
        (quote (args ...)) (quote (types ...))))
    ;; add procedure properties via an inner procedure
    (let ((helper (lambda* (args ...) body ...)))
      (set-procedure-properties! procname (procedure-properties helper))
      ;; record the types
      (set-procedure-property! procname 'return-type ret?)
      (set-procedure-property! procname 'argument-types (list types ...))
      ;; preserve the name
      (set-procedure-property! procname 'name 'procname))))

This supports most features of regular define like docstrings, procedure properties, multiple values (thanks to Vivien!), keyword-arguments (thanks to Zelphir Kaltstahl’s contracts), and so forth.

Basic usage:

(define-typed (hello typed-world) (string? string?)
  typed-world)
(hello "typed")
;; => "typed"
(hello 1337)
;; => type error ~a ~a #<procedure string? (_)> 1337
(define-typed (hello typed-world) (string? string?)
  "typed" ;; docstring
  #((props)) ;; more properties
  1337) ;; wrong return type
(procedure-documentation hello)
;; => "typed"
(procedure-properties hello)
;; =>((argument-types #<procedure string? (_)>)
;;    (return-type . #<procedure string? (_)>) 
;;    (name . hello) (documentation . "typed") (props))
(hello "typed")
;; type error: return value ~a does not match ~a (1337) #<procedure string? (_)>

Multiple Values and optional and required keyword arguments:

(define-typed (multiple-values num) ((λ(a b) (> a b)) number?)
  (values (* 2 (abs num)) num))
(multiple-values -3)
;; => 6
;; => -3
(define-typed (hello #:key typed-world) (string? #:key string?) "typed" #((props)) typed-world)
(hello #:typed-world "foo")
;; => "foo"
;; unused keyword arguments are always boolean #f as input
(hello)
;; => type error ~a ~a #<procedure string? (_)> #f
;; typing optional keyword arguments
(define (optional-string? x) (or (not x) (string? x)))
(define-typed (hello #:key typed-world) (string? #:key optional-string?)
  (or typed-world "world"))
(hello)
;; => "world"
(hello #:typed-world "typed")
;; => "typed"
(hello #:typed-world #t)
;; => type error ~a ~a #<procedure optional-string? (x)> #t
;; optional arguments
(define-typed (hello #:optional typed-world) (string? #:optional optional-string?)
  (or typed-world "world"))
(hello)
;; => "world"
(hello "typed")
;; => "typed"
(hello #t)
;; => type error ~a ~a #<procedure optional-string? (x)> #t

define-typed automates some of the guards of Optimizing Guile Scheme, so the compiler can optimize more (i.e. if you check for real?) but keep in mind that these checks are not free: only typecheck outside tight loops.

They provide a type boundary instead of forcing explicit static typing.

Also you can do more advanced checks by providing your own test procedures and validating your API more elegantly, but these then won’t help the compiler produce faster code.

But keep in mind that this does not actually provide static program analysis like while-you-write type checks. It’s simply syntactic sugar for a boundary through which only allowed values can pass. Thanks to program flow analysis by the just-in-time compiler, it can make your code faster, but that’s not guaranteed. It may be useful for your next API definition.

define-typed: a static type syntax-rules macro for Guile to create API contracts and help the JIT compiler create more optimized code.

[2024-05-09 Do]

ArneBab 2020-08-25 Di 00:00 - Impressum - GPLv3 or later (code), cc by-sa (rest)