Local Variables in Scheme / LilyPond

Urs thought it might be useful if I provided some background on this alternative to the colorGrobs and uncolorGrobs functions (see previous scheme tutorial):

#(define (wrap-music-list music-list)
  (let ((out #{ {} #} ))
    (ly:music-set-property! out 'elements music-list)
    out))

colorGrobs =
#(define-music-function (parser location grobs color)
  (symbol-list? color?)
  (wrap-music-list (map (lambda (arg) #{ \colorGrob $arg $color #}) grobs)))

uncolorGrobs =
#(define-music-function (parser location grobs)
  (symbol-list?)
  (wrap-music-list (map (lambda (arg) #{ \uncolorGrob $arg #}) grobs)))

The colorGrobs and uncolorGrobs functions should be understandable at this point. However there are a few concepts which need a little more background in order to fully understand the wrap-music-list function.

Local Scope

(This section deals just with scheme)

Up to now you’ve seen define used to create global functions and variables. Having to define everything globally has a few problems:

  • If something is only used within a single function it doesn’t make sense to define it globally–its definition is separated from it usages.

  • It’s easy to accidentally use the same name in two spots with two different meanings.

As an alternative we can define variables which are only valid for a small section of code. This section is called the ‘scope‘ of the variable.

You’ve already been introduced to lambda. It can be used to introduce variables and scope:

((lambda (a b)
  (display (+ a b))) 1 2)

This creates a function which takes two arguments and executes it immediately. The usage of the variables a and b is only valid within the body of the lambda. This certainly does what we want, but the syntax is a bit cumbersome. Luckily scheme has a more convenient form which is equivalent:

(let ((a 1)
      (b 2))
  (display (+ a b)))

A phrase that programmers use for things like this is ‘syntactic sugar’. let doesn’t make scheme any more powerful, but it is much more convenient to use and matches our intent better. Here’s the overall form for let blocks:

(let ((var1 expression1)
      (var2 expression2)
      ...)
  ...code using local variables...)

Like everything in scheme it is a list of things enclosed by parentheses:

  • let introduces the block.
  • A list of variables name/expression pairs. Each expression is executed and the result is bound to the variable name. Notice that this whole section is enclosed in parentheses and each individual variable definition is enclosed in parenthesis.
  • The code which is executed. This is the scope of the variables. They are valid only for this section.

Aside: Programmers have explored different rules for scoping variables:

  • Scheme uses ‘lexical scoping’. This means that you can see where a variable is defined by just looking at the text of the program.

  • An alternative is ‘dynamic scoping’. This means that variables are defined within a function and within all called functions.

I’m of the opinion that lexical scoping makes a program easier to understand: you don’t need to know anything about the caller’s context to understand what variables might be valid. Most popular programming languages use lexical scoping, but there are a few which still use dynamic scoping–most notably emacs lisp.


Local variables can help us reuse local computations, gather our thoughts, and label items, and avoid large nested lists which are sometimes difficult to follow. Compare:

(list
  (/ (+ (- b) (sqrt (- (* b b) (* 4 a c)))) (* 2 a))
  (/ (- (- b) (sqrt (- (* b b) (* 4 a c)))) (* 2 a)))

This is solving the quadratic equation. There’s some duplication and we can give more meaning to the variables:

(let ((denominator (* 2 a))
      (sqrt-discriminant (sqrt (- (* b b) (* 4 a c)))))
  (list
    (/ (+ (- b) sqrt-discriminant) denominator)
    (/ (- (- b) sqrt-discriminant) denominator)))

This is slightly more flexible and hopefully a bit more understandable. (This is a simple example which still has some duplication, but hopefully this gets the idea across.)

There is an issue with let though: when defining variables you can’t use other variables defined in the same let. For example this isn’t valid:

(let ((a 1)
      (b (+ a 1)))
  ...)

We’re trying to use a when defining b. This isn’t allowed.


Aside: To understand why this isn’t allowed look back at the lambda equivalent of the let block. Here’s the equivalent of the above let block.

((lambda (a b)
   ...)
  1
  (+ a 1))

There is no a variable in scope for where it is defining the value for b.


To accomplish this you can nest let blocks like this:

(let ((a 1))
  (let ((b (+ a 1)))
    ...))

This becomes cumbersome so scheme has more syntactic sugar to do this:

(let* ((a 1)
       (b (+ a 1)))
  ...)

With let* any variable can use a previously defined variable in its definition.


Aside: For completeness there is one more let form: letrec. With let* we can only use previously defined variables. With letrec we can define mutually recursive functions. Within these functions all variables defined within the letrec are available. An example:

(letrec ((sum-sq (lambda (x)
                (if (zero? x)
                  0
                  (+ (square x) (sum-sq (- x 1))))))
         (square (lambda (x) (* x x))))
  (sum-sq 5))

The sum-sq function calls itself recursively and the later defined ‘square’ function.


Lilypond Internals

Let’s turn back to lilypond now. At an extremely high level Lilypond translates your code into an internal data structure and then translates that into your output (PDF, MIDI, etc.). You can see this internal structure with the \displayMusic function.

\displayMusic { c4 d e f }

When running this through lilypond the PDF is generated like normal, but you’ll also get this in the output log:

(make-music
  'SequentialMusic
  'elements
  (list (make-music
          'NoteEvent
          'duration
          (ly:make-duration 2 0 1)
          'pitch
          (ly:make-pitch -1 0 0))
        (make-music
          'NoteEvent
          'pitch
          (ly:make-pitch -1 1 0)
          'duration
          (ly:make-duration 2 0 1))
        (make-music
          'NoteEvent
          'pitch
          (ly:make-pitch -1 2 0)
          'duration
          (ly:make-duration 2 0 1))
        (make-music
          'NoteEvent
          'pitch
          (ly:make-pitch -1 3 0)
          'duration
          (ly:make-duration 2 0 1))))

The make-music elements are composed of a type followed by key/value
pairs. Here’s a simpler lilypond expression:

\displayMusic { }

Which results in:

(make-music
  'SequentialMusic
  'elements
  '())

We can see that {} is a SequentialMusic type and it has an empty elements list. The next thing to figure out is how to modify this structure. A set of lilypond functions can be found in LilyPond’s reference. There we find the function

(ly:music-set-property! mus sym val)

. We can manually set the elements in the SequentialMusic using this function.


Side note: scheme functions which end in ! denote operations which modify data in some way. Lilypond for the most part follows this scheme. So the ly:music-set-property! function modifies the music in place.


Conclusion

Ok with that hopefully the function is understandable:

#(define (wrap-music-list music-list)
  ; Store an empty SequentialMusic instance in a local variable 'out'.
  (let ((out #{ {} #} ))
    ; Set the elements of out to be the passed in music list.
    (ly:music-set-property! out 'elements music-list)
    ; Return the modified music structure.
    out))

First we define a local variable out to be an empty music expression. Then we set its 'elements to be the list of single music expressions created by our colorGrobs and uncolorGrobs functions, and finally we return out as the result of our function.

5 thoughts on “Local Variables in Scheme / LilyPond

  1. Ralf Mattes

    Hmm, maybe I’m taking away the punch line of a future post here,
    but why do you dispatch back to lilypond syntax in your let form?

    (let ((out #{ {} #} ))

    could easlily be replaced by:

    (let ((out (make-music 'SequentialMusic
                 'elements '())))

    And then we soon find out that wrap-music-list collapses to:

    #(define (wrap-music-list music-list)
       (make-music 'SequentialMusic 'elements music-list))

    Or did I miss something?

    Reply
    1. Jay Anderson Post author

      Nope, that looks fine. However, I prefer to keep things in lilypond as much as possible. As soon as things become slightly more complicated using the ‘(make-music …)’ can become unwieldy. Another reason to stay at the lilypond level is to future proof the program. If the developers decided that they need to change the internal syntax then the program would still work. (Obviously this isn’t perfect since we’re still setting internal properties on a lilypond object.)

      Reply
  2. Urs Liska

    Thanks for all your work.
    I think through the new post and all the comments we have gathered a lot of information on how to use Scheme in LilyPond, including alternative approaches.

    As it has become somewhat complex by now to put all these pieces together I have compiled a new version of the file, offering a solution that integrates all comments. You can simply download the file and use it, it’s self-contained.

    Reply
  3. Janek Warchoł

    Thanks for this post! I wish i could have read this when i was beginning my own journey with Scheme in LilyPond 🙂

    Reply
  4. David Kastrup

    Well, wrapping a music list in sequential music can be done quite more LilyPondish by using a spliced Scheme expression:

    #(define (wrap-music-list music-list) #{ #@music-list #})

    And of course, wrap-music-list is already available as LilyPond’s make-sequential-music but then if one doesn’t know this, it is not helpful.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *