Useful SRFIs for Scheme Programming

An SRFI is a “Scheme Request for Implementation”, a standard way in which the community extends the functionality of the Scheme programming language. Today I want to briefly talk about some SRFIs that I’ve found useful. Example uses of the SRFIs I’ll discuss can be found in a script I wrote for use with the Scheme Shell.

SRFI-2: and-let*

This first SRFI is the work of Oleg Kiselyov, a computer scientist who’s written a number of papers on functional programming and has worked on books such as “The Reasoned Schemer”.

A common scenario is to try and compute a value and, if successful, use that value in following expressions. Depending on those expressions this can be costly as you may end up computing said value over and over for each expression. For example, the script I linked to above works with ‘records’ (similar to structures in C) which may contain their own functions, such as their install-function property. If a given record has a value for that property then I want to call it, but first I need to see if the value exists and make sure that it has the correct type.

(and (get-install-function software)
     (procedure? (get-install-function software))
     (apply (get-install-function software) (list software)))

Calling get-install-function three times for this is not ideal. We could instead write it like this:

(let ((install-function (get-install-function software)))
  (and install-function
       (procedure? install-function)
       (apply install-function (list software))))

This calls get-install-function only once, binding it to a name we can use throughout the and expression. This is a much better approach because we avoid invoking a potentially costly function multiple times.

and-let* lets us take this approach with a single construct:

(and-let* ((install-function (get-install-function software))
           ((procedure? install-function))
           ((apply install-function (list software)))))

Each form within and-let* can create a binding usable by the following forms. In the example above, the first form binds install-function, which the other forms go on to use. This is the behavior of let*, but like and, the and-let* construct will also terminate the moment any expression evaluates to a boolean false value.

I will admit that my own example doesn’t look like a useful gain, but if your and forms involve the computation of multiple values then and-let* really shines in its usefulness.

SRFI-9: Record Types

As I said above, records in Scheme are similar to structures in other languages. There have been various implementations of records in Scheme which still exist today. But SRFI-9 provides a way to create records that is compatible across different Schemes and platforms.

Here’s an example from my own program:

(define-record-type :software
  (new-software directory install-prefix)
  software?
  (directory get-directory)
  (install-prefix   get-install-prefix)
  (update-function  get-update-function  set-update-function!)
  (build-function   get-build-function   set-build-function!)
  (install-function get-install-function set-install-function!))

This form accomplishes the following:

  1. It defines the type :software for the record.

  2. It defines the constructor (new-software …) which accepts two arguments. It will create and return a new :software record with two of its properties set to the values given to the constructor.

  3. The function software? is a predicate of one argument that is true for all instances of the record and false for everything else.

  4. All of the other forms define the properties of the record. Their syntax is:


(name getter [setter])

In my example the record has five properties. Two have only getters, functions for accessing the value of those properties from instances of the record; they have no setters because those two properties are set as part of the constructor. The other three properties have both getter and setter functions; they are optional and none are set by the constructor, hence the definition of both getters and setters.

Creating one of the records looks like so:

(define emacs (new-software "/home/eric/Software/Emacs" "/usr/local"))

;; (get-directory emacs)      => "/home/eric/Software/Emacs"
;; (get-install-prefix emacs) => "/usr/local"

And an example of a function which uses other getters of the record along with the aforementioned and-let* form:

(define (update software)
  (with-cwd (get-directory software)
   (or (and-let* ((update-function (get-update-function software))
                  ((procedure? update-function))
                  ((apply update-function (list software)))))
       (begin
         (run (git fetch origin))
         (run (git checkout master))
         (run (git merge --ff-only origin/master))))))

Note: This is also an example of using (with-cwd …) and (run …) in the Scheme Shell to execute shell commands within a given directory.

SRFI-13: Useful String Functions

The final SRFI that I want to discuss is the most complex of the three simply in terms of the amount of functions it defines. R5RS Scheme, the standard used by the Scheme Shell, provides little in the way of string functions. There are functions for creating strings, simple modifications and access to sub-strings, and for performing basic comparison. This reflects Scheme’s philosophy of minimalism, especially when compared to other Lisps like Common Lisp and all the symbols it defines, to say nothing of third-party additions by implementations like Steel Bank Common Lisp (SBCL).

(Note: SBCL is a terrific implementation of Common Lisp in my opinion, if you ever have need for one.)

Nearly all applications work with strings in some capacity. When using Scheme for a task such as writing shell scripts the paucity of Scheme’s string functions quickly becomes apparent. SRFI-13 builds upon Scheme’s string library by adding a number of useful utilities, such as:

  • string-null?: A predicate that tells you if a string is empty or not.

  • string-join: Takes a list of strings and combines them, inserting a delimiting string between each element, e.g.

    (string-join '("usr" "local" "bin") "/" 'prefix)
    
    ;; Evaluates to: "/usr/local/bin"
  • string-trim: Removes characters from the start and end of a string, and by default removes whitespace.

  • string-prefix?: A predicate to determine if a string begins with a given prefix. The SRFI also provides string-suffix?, and versions of both which perform case-insensitive tests.

  • string-contains: Tells you if a sub-string appears within a string, and if so, the start and end indices for that sub-string.

  • string-tokenize: Splits a string into a list of sub-strings based on delimiters of your choice, defaulting to whitespace.

Such utilities are common fixtures in the standard libraries of many high-level programming languages. Personally I like Scheme’s minimalism and the simplicity it affords the language. But the string functions of R5RS Scheme are severely lacking, making SRFI-13 a treasure chest of valuable tools.

Conclusion

If you use Scheme then I strongly recommend taking a look at which SRFIs your choice implementation supports. Many of them are quite useful, so I also suggest reading the full list of accepted SRFIs. A lot of them can be implemented as macros in the event you’re using a Scheme that does not provide a certain SRFI, and every SRFI comes with details about how you can implement it if needed.

Advertisements

Add Your Thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s