Search code examples
moduleschemeguile

Should I define separate module for every file in my Guile project?


Let me explain my problem by comparison. In Common Lisp I could split package definitions to several files, it was enough to declare in each of them that it's in-package and load them.

However in Guile Scheme it looks like I should define-module, separate for each file? Well I still can load some files like in CL and it looks like working, define-modules seems not limited to a single file it is located in like in CL, but I get warnings about undefined names (those that are defined in loaded files), so it gives me feeling that it's not what Guile expects. Is there (1) some way of splitting module across several files like in CL, or (2) should I stick to use-module autoload feature and define-module for each file separatelly?


Solution

  • Indeed in Guile you can load inside a define-module but it will report unbound variable at compile time.

    The idiomatic way is the define-module in every file:

    ;; in earth-software-system.scm
    (define-module (earth-software-system))
    
    (use-modules (earth-software-system bullet-train))
    (use-modules (srfi srfi-9))
    
    (re-export bullet-train) ;; possibly re-exporting imported bindings
    
    ...
    

    Then in earth-software-system/bullet-train.scm you can have:

    ;; in earth-software-system/bullet-train.scm
    (define-module (earth-software-system bullet-train))
    
    (use-modules (srfi srfi-9))
    
    (define-public bullet-train 42)
    
    ...
    

    Mind the fact that define-public and a single import per use-modules is not widespread. Here is an example from GNU Guix project that rely on define-module to import and export:

    (define-module (guix cpio)
      #:use-module ((guix build utils) #:select (dump-port))
      #:use-module (srfi srfi-9)
      #:use-module (srfi srfi-11)
      #:use-module (rnrs bytevectors)
      #:use-module (rnrs io ports)
      #:use-module (ice-9 match)
      #:export (cpio-header?
                make-cpio-header
                file->cpio-header
                file->cpio-header*
                write-cpio-header
                read-cpio-header
    
                write-cpio-archive))
    

    Also nowadays I prefer the import form which is more easy to the mind that use-modules:

    ;; in earth-software-system.scm
    (define-module (earth-software-system))
    
    (import (prefix (earth-software-system bullet-train) 'bt:)
    (import (srfi srfi-9))
    
    (re-export bt:bullet-train) ;; possibly re-exporting imported bindings
    
    ...
    

    The prefix syntax is also more easy to the mind than the equivalent using use-modules. This is inspired from R6RS library form and R7RS define-library form. I do not recommend to use library form in Guile since it doesn't report lines correctly.

    GNU Guile allow to import forms even if they are not exported using the @@ syntax for instance to test some tricky behavior.

    You might replace load with include but I never used it in Guile