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.

\version "2.25.8"

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

{ c' }

% #myVar % Error
Output../../images/4a240e2bfa9010576007432ce45e31d623a8f39781b913067c23e915e6ebe57d.svg

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:

\version "2.25.8"

myPitch = c
{ $myPitch 4 }
Output../../images/457ab4c4e3cf6554a7a12093e043ed754c1f5c458c78b29e48dc48b4c7c30073.svg

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. From the parser’s point of view, the code { $myPitch 4 } is (almost) equivalent to { c 4 }.

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

\version "2.25.8"

myPitch = c
{ #myPitch 4 } % Error: ignoring non-music expression
Output../../images/90658117724ae6bd73b50f17cf3df1cbe2df29ce6aa7eac144e5e007934d778d.svg

Indeed, what the parser now sees is a Scheme expression for evaluation, which is not the same as a pitch. The parser has no way of knowing that this Scheme expression will return a pitch at the moment it has to decide whether to treat it as a pitch, which is earlier than it wants to evaluate it.

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

\version "2.25.8"

myPitch = c
#(display myPitch)
Output../../images/0605c58f1f3a308d27b88a2e433f38ebda9a6d8b9639f04b90b371faeb6c5888.svg

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. But the lexer, seeing a Scheme expression introduced with $, tries to evaluate it immediately, which cannot work as the assignment of the variable myPitch has not happened yet.

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.