LilyPond and Scheme

The primary way to insert a piece of Scheme code in LilyPond is the hash character. It introduces one complete expression, which is evaluated. Its value is inserted in the LilyPond context. The special value *unspecified*, returned by expressions without a meaningful value like define clauses, is ignored.

Scopes

LilyPond scopes are also Guile scopes. Variables defined in LilyPond are accessible from Scheme code and vice versa. Moreover, certain blocks define scopes of their own. This is the case with \layout and \paper among others.

\layout {
  #(define myVar 5)
  indent = \myVar
}

{ c' }

% #myVar % Error

Hash vs. dollar

The $ characters is a second way to insert Scheme expressions. It works a bit differently:

  • It makes a copy of the value.

  • It takes effect earlier during parsing.

The second point is slightly intricate. LilyPond uses a lexer and a parser to understand input syntax. The lexer understands tokens, much like words, and the parser understands the structure, much like the grammar in a sentence. The parser requires some look-ahead in the lexer. Think of completing the sentence “This is a kind …”. This might end as “This is a kind of ice cream.” or “This is a kind person.” In the first sentence, the word “kind” is a noun, whereas it is an adjective in the second sentence. It is necessary to see the next word to determine how to interpret “kind”.

With $, the Scheme code is interpreted right when the lexer sees it. This is convenient to build fundamental elements in the input with Scheme. For example:

myPitch = c
{ $myPitch 4 }

This code works! The variable myPitch is replaced immediately with the pitch C by the lexer, and the parser is completely oblivious to this substitution. It sees a pitch followed by a duration and understands it as a note.

Replacing the dollar with a hash sign in this example causes it to fail.

myPitch = c
{ #myPitch 4 } % Error: ignoring non-music expression

Indeed, what the parser now sees is a Scheme expression for evaluation, which is not the same as a pitch.

On the other hand, the use of the dollar can have unexpected effects due to look-ahead. This works:

myPitch = c
#(display myPitch)

It does no longer work when the hash is replaced with a dollar:

myPitch = c
$(display myPitch) % Error: unbound variable: myPitch

This is because the parser queries an extra token after the assignment to be sure how to interpret it – if it were “8” for example, “c 8” would be interpreted as a note rather than a mere pitch. The evaluation of the Scheme expression by the lexer occurs before the assignment has happened.

Thus, the dollar allows LilyPond syntax to be understood in a way dependent from the values the Scheme expressions yield. This has the downside that the expressions must be evaluated early.

As a conclusion, you should generally use the hash, with a few exceptions for the dollar in case you want to play syntax tricks or copy a value conveniently.