Search code examples
moduleschemerecorddata-storageguile

Store external data in guile modules?


Suppose I have a collection of external data structures in a txt file. Whenever I need the data for computation I have to parse the file and then use it's values. How to make it more convenient?

  1. Keep the once parsed data as a complex list structure and write it in a SCM file with write. Then the full list can be simply read with read. Sure, but... (everytime the complex list representation of the data would have to be read and all the records would have to be formed afterwards - such could be avoided if the data had already been compiled)
  2. Can't I somehow keep the parsed result in a guile module? That is a compiled stuff once for all? I cannot find the right way to do it, though.

A simplified example:

Let the complex data be just a single number in an external file DATA.txt:

10

Let's create a module data.scm that reads in the external data, stores it and exports it:

(define-module (data))

(export DATA)

(define DATA (with-input-from-file "DATA.txt" read))

Let's test this approach with test.scm:

(add-to-load-path (dirname (current-filename)))

(use-modules (data))

(format #t "DATA: ~a\n" DATA)

THIS SEEMS TO WORK (auto-compilation is on or even guild compile data.scm is used):

$ guile test.scm
DATA: 10

However, when I delete the DATA.txt file, an error is raised since the (compiled) data module is missing the DATA.txt file for reading!

So the problem prevails: How to store the external data in the module at compile-time, actually?

WHAT WORKS - BUT IS UGLY!

To generate the module file literally, like:

(define DATA (with-input-from-file "DATA.txt" read))

(with-output-to-file "data.scm"
  (lambda ()
    (format #t
            "(define-module (data))

(export DATA)

(define DATA ~a)\n"
            DATA)))

This approach is like using the C macros and not taking advantage of the scheme's macros (i.e. syntax). Is there really no elegant scheme way?


Solution

  • Here's an example of doing it using a syntax-case macro:

    $ cat foo.scm
    (define-module (foo)
      #:export (data))
    
    (use-modules (ice-9 textual-ports))
    
    (define-syntax include-file
      (lambda (stx)
        (syntax-case stx ()
          ((_ name)
           (let ((file-contents
                  (call-with-input-file (syntax->datum #'name) get-string-all)))
             (datum->syntax #f file-contents))))))
    
    (define data (include-file "./data.txt"))
    $ cat data.txt
    blah
    $ sudo cp foo.scm /usr/share/guile/site/3.0/foo.scm
    $ sudo guild compile -o /usr/lib64/guile/3.0/ccache/foo.go foo.scm
    $ cd ..
    $ guile
    GNU Guile 3.0.9
    Copyright (C) 1995-2023 Free Software Foundation, Inc.
    
    Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
    This program is free software, and you are welcome to redistribute it
    under certain conditions; type `,show c' for details.
    
    Enter `,help' for help.
    scheme@(guile-user)> (use-modules (foo))
    scheme@(guile-user)> data
    $1 = "blah\n"
    

    As you can see, after copying the source file to somewhere in guile's load path and compiling it to bytecode ahead of time, you can load the module from other locations (Or delete the data file) and the data is still available. But, really, it's easier to just have the data directly in a scheme file (Perhaps with the help of a program that creates such a file from the raw data that can be run when the data updates) and using guile's auto-compilation is a lot easier and less involved.