Abhängig davon, ob eine Variable definiert ist,...

Begonnen von ingmar, Sonntag, 10. Juni 2018, 21:39

« vorheriges - nächstes »

ingmar

In diesem Beitrag hatte Harms gezeigt, wie man einen Variablennamen in Scheme zusammenbauen kann, um den Inhalt dieser Variable dann in den LilyPond-Code "einzufliegen":

\version "2.19.64"

A-music = \relative { c'4 d e f g1 }
A-titel = "c bis g"

B-music = \relative { g'4 f e d c1 }
B-titel = "g bis c"

myscore = #(define-scheme-function ( pointer) ( string?)
#{ \score {
    \header {
      piece = #(eval-string (format #f "~a-titel" pointer))
    }
    \new Staff { #(eval-string (format #f "~a-music" pointer)) }
}
#})

\myscore "A"
\myscore "B"


Mit diesem Ansatz bin ich ein ganzes Stück weit gekommen. Ich habe aber zwei Fälle gefunden, wo mir doch noch etwas Wesentliches fehlt:

Fall 1: Ich möchte für den Fall, dass die anstehende Variable nicht definiert ist, einen Defaultwert festlegen. – Codierungsversuch:

\version "2.19.64"

C-music = \relative { f'4 a c f, e1 }
C-titel = "Definierter Titel:" % <-- diese Zeile auskommentieren!

myscore = #(define-scheme-function ( pointer) ( string?)
  (if (not (defined? (eval-string (format #f "'~a-titel" pointer))))
    (let (eval-string (format #f "'~a-titel" pointer)) "Default-Titel"))
#{ \score {
    \header {
      piece = #(eval-string (format #f "~a-titel" pointer))
    }
    \new Staff { #(eval-string (format #f "~a-music" pointer)) }
  }
#})

\myscore "C"


Dies läuft ebenfalls problemlos - bis man die dritte Zeile "C-titel" auskommentiert. Fehlermeldung lautet "Bad binding eval-string". Die Zeile, die mit (let.. beginnt, hat offenbar noch einen Fehler. Kann es wieder das alte Problem sein, dass man eine Variablendefinition innerhalb des \score einfach nicht vornehmen darf?


Fall 2: Ebenfalls abhängig davon, ob eine Variable definiert ist, möchte ich innerhalb der \score- Definition einen \this-Abschnitt anlegen oder nicht. – Codierungsversuch:

\version "2.19.64"
D-music = \relative { e'4 g c e, f1 }
D-instrument = "Saxophon"

myscore = #(define-scheme-function ( pointer) ( string?)
#{ \score
  #(if (defined? (eval-string (format #f "'~a-instrument" pointer)))
    ( #{ \with instrument = #(eval-string (format #f "~a-instrument" pointer)) #})
  {
\new Staff { #(eval-string (format #f "~a-music" pointer)) }
  }
#})

\myscore "D"

...das schlägt gleich richtig fehl; ich hab verschiedene Varianten probiert, finde aber keinen Weg, ein \with { ... } von Bedingungen abhängig zu machen.

habt Ihr einen Tip? Sicher ist die Lösung wieder ganz einfach... : - )

Danke und Gruß,
--ingmar

harm6

Hallo Ingmar,

mittlerweile denke ich, daß (eval-string ...) nicht die beste Wahl ist.
ly:parser-lookup scheint mir momentan vielversprechender, auch wenn man strings zu symbols umwandeln muß, was umständlich ist.


\version "2.19.64"

%D-music = \relative { e'4 g c e, fis1 }
%D-titel = "Definierter Titel:"
%D-instrument = "Saxophon"

myscore = #(define-scheme-function (pointer) ( string?)
#{
  \score {
    \header {
      piece =
        #(let ((sym (string->symbol (format #f "~a-titel" pointer))))
          (if (defined? sym)
              (ly:parser-lookup sym)
              "foo"))
    }
    \new Staff
      \with {
        instrumentName =
          #(let ((sym (string->symbol (format #f "~a-instrument" pointer))))
            (if (defined? sym)
                (ly:parser-lookup sym)
                '()))
      }
      {
          #(let ((sym (string->symbol (format #f "~a-music" pointer))))
            (if (defined? sym)
                (ly:parser-lookup sym)
                (empty-music)))
      }
  }
#})

\myscore "D"


Gruß,
  Harm

ingmar

#2
Vielen Dank, Harm!

Das nötige Umwandeln in Symbole macht mir nichts aus. Deine Beispiele funktionieren hervorragend; wenn ich sie dann in meinen Code einbaue, gibts allerdings wieder Fehlermeldungen, obwohl ich mir einbilde, es genauso zu machen... Ich werde natürlich versuchen, diese Fehler zu analysieren und zu Minimalbeispielen einzudampfen.

Malte hatte in seinem ersten Post ly:parser-include-string verwendet, du hast erst eval-string, dann ly:parser-lookup verwendet. Ich frage mich natürlich, was die Unterschiede sind – und ob ich sie verstehen würde...

Gruß,
--ingmar

harm6

Soweit ich das auf die Schnelle sagen verlangt ly:parser-include-string ein string-argument, welches zu einem musikalischen Ausdruck evaluiert werden kann, ist also seht limitiert.
Später hatte Malte (module-ref (current-module) ...) verwendet. Aber genauso wie eval-string gibts einen error wenn das Argument nicht definiert ist.
ly:parser-lookup jedoch retourniert '() für ein nicht definiertes Argument, damit kann man besser umgehen.

Es gibt noch weitere Unterschiede, aber das ist der für die Anwendung hier entscheidende, imho.

Gruß,
  Harm

ingmar


ingmar

#5
Mein Problem hatte offenbar damit zu tun, dass du in deinem Code eine Variable sym definierst, was in meinem Zusammenhang nicht geschmeckt hat (Variablendefinition/-zuweisung geht halt nicht überall). Habs umgangen, und damit läuft alles, wie es soll.

Danke,
--ingmar

ingmar

Eine Frage ist aber noch offen! An manchen Stellen in LilyPond müssen einfach Key/Value-Paare stehen, beispielsweise innerhalb von \paper. Unabhängig einmal von der (für mich gelösten) Frage der Evaluierung von Variablennamen stellt sich hier noch die Frage nach der richtigen Syntax, wenn wir uns innerhalb eines durch Scheme erzeugten #{...#}-Blocks befinden und die in Scheme vorhandene Variable auswerten wollen.

Hier mehrere Versuche – je eine auskommentierte Zeile, die beim Entkommentieren aus verschiedenen, aber wohl ähnlichen Gründen fehlschlägt:

\version "2.19.64"

mypaper = #(define-scheme-function (mymargin) (integer?) #{
\paper {
% top-margin = \mymargin
% top-margin = #(mymargin)
% top-margin = #(eval mymargin)
% top-margin = #(ly:parser-lookup (mymargin))
}
#})

\mypaper 30
\score {
\relative { c' e d f e g f a g1\fermata }
}


Es sollte eigentlich nicht schwer sein, aber ich komm nicht drauf! Wie kann man das angehen, und welchen Denkfehler mache ich?


Danke, Gruß,
--ingmar

harm6

Hallo Ingmar,

eine solche lokale Variable rufe innerhalb von #{ #} mit #mymargin oder $mymargin auf.
#mymargin ruft es direkt, $mymargin eine Kopie. Hier ist der Unterschied belanglos, aber falls das Argument Musik ist, so bevorzuge $.
Ansonsten kann es passieren, daß das Musik-Argument destruktiv verändert wird und nicht mehr im Original zur Verfügung steht.

Im Übrigen
(1)
\mymargin würde eine toplevel Variable aufrufen können, keine lokale.
(2)
#(mymargin)
Durch die Klammern versucht der scheme-interpreter die procedure `mymargin` auszuführen. Eine solche procedure gibt es aber nicht.
(3)
#(eval mymargin)
Hier ist die procedure `eval`, die versucht `mymargin` innerhalb einer nicht gegebenen "Umgebung" zu evaluieren.
Deshalb der error:
Wrong number of arguments to #<primitive-procedure eval>
#(eval mymargin (current-module)) würde funktionieren, aber warum der Umstand...
Eine Zahl evaluiert zu nichts anderem als eben dieser Zahl
(4)
#(ly:parser-lookup (mymargin))
Funktioniert nicht wegen der Klammern, siehe oben.
ly:parser-lookup erwartet ein symbol (den Namen dessen was Du im parser nachschauen möchtest)
(ly:parser-lookup 'mymargin) wäre zwar korrekte Syntax, scheitert aber da `mymargin` eine lokale Variable ist, die der parser gar nicht zu sehen bekommt, afaict.


Gruß,
  Harm


ingmar

Danke! Bei einigen davon hätte ich ja selber draufkommen können.. : - (

Nachher ist man immer klüger.

Gruß,
--ingmar

ingmar

Noch eine Frage: Wenn eine (globale) Variable noch nicht definiert ist, möchte ich dies tun. Auch hier möchte ich den Namen 'berechnen'.

Ich hab mir folgenden Code ausgedacht:

\version "2.19.64"

mymusic = { d2 d } % auskommentieren!

AA = #(define-scheme-function (prefix) ( string?)
(if (not (defined? (eval-string (format #f "'~amusic" prefix))))
(define (eval-string (format #f "~amusic" prefix)) #{ c1 #})))

\AA "my"

\score { \mymusic }


Das funktioniert prima - bis ich Zeile 3 auskommentiere und damit die Definition verstecke. Dann sollte sie eigentlich den Wert { c1 } bekommen - aber das funktioniert nicht. Die Fehlermeldung spricht von "procedure memoization"...

Tja, weiß jemand weiter? – Danke, und Gruß,
--ingmar

harm6

Hallo ingmar,

ein paar Dinge vorweg.

(1)
Dein Code versucht ja eine Variable zu setzen, falls sie nicht bereits definiert ist. Deine Methode ist (aufs wesentliche runtergebrochen):


#(if (not (defined? 'foo))
     (define foo '()))


Damit scheint es irgendein Problem zu geben.
In nativem guile-1.8 gehts zwar, aber in der ly-function nicht.

Versucht man das in guile-v2 bekommt man eine aussagekräftigere error-message:
"definition in expression context, where definitions are not allowed,"

Ich habe mal unseren source-code auf "defined?" hin untersucht und fand das nirgendwo mit (define ...) fortgesetzt wird sondern mit
module-define!
Das scheint mir auch hier ein Weg zu sein. Wiewohl man das Ziel-module angeben muß, was vielleicht manchmal in Frage steht.
Aber es gibt ja auch ly:parser-define!, s.u.

(2)
`eval-string`ist generell mit Vorsicht zu genießen, neben anderem ist es auch aufwändig.
Im Fall Deines Codes ist es auch nicht sinnvoll.

Nehmen wir als Beispiel die Funktion `shape`
#(write (eval-string "shape"))
führt zu
#<Music function #<procedure #f (offsets item)>>
Wie erwartet.

Afaiu transformiert eval-string intern den string zu einem symbol und sucht diesen als Namen in (current-module).
Aber ein string ist ein Datenformat, im Gegensatz zu einem Symbol oder einer Zahl. Ein symbol oder eine Zahl evaluiert aber immer nur zu sich selbst.

Das bedeutet, daß  (eval-string "'shape") erst zu ''shape transformiert wird, bzw der besseren Lesbarkeit wegen: (quote (quote shape))
Und letztlich kommt dann 'shape raus, nicht aber die Definition dafür.

Genauso für eine Zahl:
(eval-string "4711")
=> 4711

Besser wäre es string->symbol zu verwenden und dann via `defined?` weiter zu machen.

(3)
Dann kann man sich auch dieses mehrfache eval-string schlichtweg sparen.

(4)
Du willst ja gar kein return-value bekommen, also besser define-void-function.



Führt zu:


\version "2.19.82"

% mymusic = { d2 d } % auskommentieren!

AA =
#(define-void-function (prefix) (string?)
  (let ((my-symbol (string->symbol (format #f "~amusic" prefix))))
(if (not (defined? my-symbol))
(module-define! (current-module) my-symbol #{ c1 #})
;; or:
;(ly:parser-define! my-symbol #{ c1 #})
)))

\AA "my"

\score { \mymusic }


HTH,
  Harm



ingmar

Hallo,

erstmal danke - es scheint sehr gut zu funktionieren. : - )

Eine Frage noch:
(module-define! (current-module) my-symbol #{ c1 #})
;; or:
;(ly:parser-define! my-symbol #{ c1 #})


Verstehe ich das richtig, dass (module-define! (current-module)... das gegenwärtige Modul identifiziert und die Variable mit Sichtbarkeit innerhalb dieses Moduls anlegt, wohingegen (ly:parser-define! ... die Variable global erzeugt? Könnte  man damit also vielleicht globale Variablen auch innerhalb der berühmten geschweiften Klammern { } setzen?


Gruß, danke,
--ingmar

harm6

Zitat
Verstehe ich das richtig, dass (module-define! (current-module)... das gegenwärtige Modul identifiziert und die Variable mit Sichtbarkeit innerhalb dieses Moduls anlegt, wohingegen (ly:parser-define! ... die Variable global erzeugt?
`module-define!` braucht ein module als Argument, es identifiziert nichts von sich aus.
Inwieweit ansonsten Unterschiede zwischen beiden Möglichkeiten habe ich nicht erkundet...

Zitat
Könnte  man damit [ly:parser-define!] also vielleicht globale Variablen auch innerhalb der berühmten geschweiften Klammern { } setzen?
Arnold hat bereits in dieser Richtung gerbeitet.
https://lilypondforum.de/index.php/topic,195.msg1231.html
Aber les' auch die Diskussion hier
https://lists.gnu.org/archive/html/lilypond-user/2017-12/msg00439.html

Gruß,
  Harm