lilypond-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Two optional arguments


From: Urs Liska
Subject: Re: Two optional arguments
Date: Wed, 15 Jul 2020 12:07:48 +0200
User-agent: Evolution 3.36.4-1

Am Dienstag, den 14.07.2020, 22:57 +0200 schrieb David Kastrup:
> Urs Liska <lists@openlilylib.org> writes:
> 
> > Hi all,
> > 
> > is it correct that you can't write a music function with two
> > optional
> > arguments in a row?
> 
> No, it isn't correct.  But you know yourself, quoting the respective
> passages from the manual.

OK, is that wording better? You can't write a music function with two
optional arguments in a row and expect the argument to behave
independently as optional arguments, regardless of which argument(s)
are left out in a call.

> 
> > If so:
> > 
> > * is that on purpose?
> 
> Yes.
> 
> > * is that inevitable?
> 
> I don't know what you call "inevitable".

With that I mean what you explain below starting with "That would be a
complete nightmare" - which amounts to a "yes" to my question.

> 
> > * is there a workaround for my use case (below)?
> > 
> > Consider this function
> > 
> > twoOpts =
> > #(define-void-function (one two three)((symbol? 'foo) (number? 5)
> > string?)
> >    (ly:message "one: ~a
> > two: ~a
> > three: ~a" one two three))
> > 
> > and this call
> > 
> > \twoOpts
> > hey 
> > 4
> > "bar"
> > 
> > This works correctly, and when I comment out the 4 it properly
> > picks
> > the default value 5.
> > However, when I comment out both or only the first value it fails
> > with
> > 
> >     error: wrong type for argument 3.  Expecting string, found 4
> 
> That is an incorrect description.  This only happens when you comment
> out only the first value.  When you comment out both optional values,
> the first value that is seen is "bar" which is a valid value for a
> symbol.  If you instead write #"bar" instead, this can only become a
> string argument and not a symbol and consequently both optional
> arguments are replaced by their default.

This is something I didn't know and which I find surprising. I would
really expect using quotation marks indicates something is a string. I
find it practical that a value without quotes can be parsed as string
or symbol if the parser knows the expectations, but explicitly adding
the quotes would seem like an explicit statement.
But I assume much thought has gone into these considerations, so I
won't question it.

> 
> > This behavious is consistent with the extending manual 
> > http://lilypond.org/doc/v2.21/Documentation/extending/index_7#Scheme-functions
> > 
> > "Once an optional argument predicate does not match an argument,
> > LilyPond skips this and all following optional arguments, replacing
> > them with their specified default, and ‘backs up’ the argument that
> > did
> > not match to the place of the next mandatory argument. Since the
> > backed
> > up argument needs to go somewhere, optional arguments are not
> > actually
> > considered optional unless followed by a mandatory argument."
> > 
> > So in 
> > 
> > \twoOpts
> > %hey 
> > 4
> > "bar"
> > 
> > The 4 doesn't match the first symbol? predicate, so all optional
> > arguments receive their defaults and the 4 is "backed-up" to the
> > next
> > mandatory argument which happens to be a string? that now fails.
> 
> Yes.
> 
> > It does work (also consistent with the docs) when each optional
> > argument is followed by its "own" backup mandatory argument.
> > 
> > So far, so bad. What I don't understand is why in case of a missing
> > optional argument (detected by a failed type check) LilyPond has to
> > skip "this and all following optional arguments". Couldn't LilyPond
> > just skip "this" and back up the failed argument to the next
> > (mandatory
> > *or* optional) argument to cascade through all available arguments.
> 
> That would be a complete nightmare because LilyPond's large number of
> default conversions (which you already got hit with when writing
> \twoOpts "bar" which then interprets "bar" as a symbol) would very
> likely make _some_ match in an unexpected place.  

Indeed, *this* is what makes the issue clear for me. You can jump from
predicate to predicate if each test is unambiguous, but if a value
might match different predicates it's of course impossible to have a
completely generic parsing system.

> By only checking
> against a single optional argument, when things blow up around your
> ears, it at least happens in a somewhat predictable place.
> 
> > In the call
> > 
> > \twoOpts 4 "bar"
> > 
> >  * 4 wouldn't match symbol?
> >  * so one would get the default value 'foo
> >  * then the 4 is checked against the next predicate, which works
> >  * so two gets the 4
> >  * finally three gets "bar"
> > 
> > In the call
> > 
> > \twoOpts "bar"
> > 
> >  * "bar" wouldn't match symbol?
> >  * so one gets the default 'foo
> >  * "bar" is then checked against the next predicate, which fails
> > too
> >  * so two gets the default 5
> >  * finally "bar" matches the mandatory argument's predicate
> > 
> > I totally understand that it is absolutely necessary to have an
> > unambiguous order of predicates when optional arguments are
> > involved,
> > but couldn't it be possible to have multiple optional ones
> > following
> > after another.
> 
> It would rarely work as expected.  Also what would
> 
> \key \default
> 
> then mean?  Omitting only the first optional argument?  That would be
> a
> significant change in meaning.  Omitting both?  Why?
> 
> > I would really like to write a function with a symbol? and a
> > ly:context-mod? argument and have both of them optional.
> > 
> > Is there any possible workaround currently?
> 
> I have no idea what you call a "workaround".  You can always use a
> mandatory argument and use some special value to indicate special
> behavior.

Well, the very idea is to avoid having to enter that special value if
all the user wants to convey is "nothing".

It would be nice if the user could write each of these (all in the
context of my other discussion about presets/configuration/subsets):

\myFrame                                c'
\myFrame analogy                        c'
\myFrame         \with { color = #red } c'
\myFrame analogy \with { color = #red } c'

It seems I would have to put a mandatory argument between the symbol
and the \with block here, which seems like a bad interface.

The solution I'll probably have to take is pulling the symbol inside
the \with block:

\myFrame \with {
  configuration = analogy
  color = #red
}

This forces the user to write a \with block when all they want is to
load a configuration. But that's not a *bad* interface, only
inconvenient, so clearly the lesser evil.

> 
> > Would this be a legitimate feature request?
> > Is there anything I've overlooked that makes this impossible?
> 
> I would very strongly discourage messing with that part of the
> design.
> It has taken a long time (basically from 2.14 to 2.20) to make all
> aspects of this design work dependably, not just in terms of it
> working
> in a logical manner and as described, but also in terms of being
> predictable and matching a large set of use cases (a whole lot of
> what
> now is implemented in terms of music functions was hardwired syntax
> previously).
> 
> Changes of the "I know, I'll just change the parser semantics for
> that
> particular use case I care about" kind will very likely lead to a
> cascading sequence of consequences that have no logical (let alone
> programmatic) solution and will be discovered by and by.

OK, to reiterate: given the issue of the ambiguity of implicit
conversions I now see why this wouldn't work reliably and generally
enough.

Urs

> 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]