lilypond-user
[Top][All Lists]
Advanced

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

Scheme syntax vs. other languages [was: Re: Appreciation / Financial sup


From: Joseph Rushton Wakeling
Subject: Scheme syntax vs. other languages [was: Re: Appreciation / Financial support]
Date: Thu, 07 Jun 2012 00:22:20 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20120430 Thunderbird/12.0.1

On 05/06/12 08:53, David Kastrup wrote:
I would doubt that this would have been the fault of Scheme.  More
likely a problem of the Scheme/LilyPond interface choices, but those
choices don't go away when replacing Scheme.

No, it was the fault of the unfamiliar Scheme syntax. A colleague used to working with Scheme was able to solve the problems I encountered trivially without reference to anything LilyPond-specific.

Python is a programming language where simple cut&paste of example code
fails unless you cut and paste whole lines since leading whitespace
matters.  I don't call that exactly easy to comprehend.

... which is finnicky and annoying, but doesn't necessarily make the code itself difficult to understand or tweak. C, C++, Java, D, Go, PHP, JavaScript and even Ruby and Python share a close enough notational style and a close enough set of supported programming paradigms (particularly imperative and object-oriented) to make it fairly easy to jump from one to another.

Scheme (and all other LISP dialects), Haskell and so on have a starkly different notational style and set of programming paradigms that make them difficult to adapt to from current mainstream programming approaches. That puts a barrier in the way of lots of potential contributors.

Anyway, show the code.  Take a snippet of LilyPond code, pretend that
LilyPond's extension language is Python, and show how it should look
like under that pretense.

I've no intention of proposing Python as the extension language, but I'll do what you suggest with D.

Here's a Scheme function, naturalize-pitch, which was written (not by me) to remove double-accidentals and enharmonically rewrite B#, E# and Cb, Fb. It can be found in:
http://lilypond.org/doc/v2.15/Documentation/notation/changing-multiple-pitches#transpose

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (naturalize-pitch p)
  (let ((o (ly:pitch-octave p))
        (a (* 4 (ly:pitch-alteration p)))
        ;; alteration, a, in quarter tone steps,
        ;; for historical reasons
        (n (ly:pitch-notename p)))
    (cond
     ((and (> a 1) (or (eq? n 6) (eq? n 2)))
      (set! a (- a 2))
      (set! n (+ n 1)))
     ((and (< a -1) (or (eq? n 0) (eq? n 3)))
      (set! a (+ a 2))
      (set! n (- n 1))))
    (cond
     ((> a 2) (set! a (- a 4)) (set! n (+ n 1)))
     ((< a -2) (set! a (+ a 4)) (set! n (- n 1))))
    (if (< n 0) (begin (set! o (- o 1)) (set! n (+ n 7))))
    (if (> n 6) (begin (set! o (+ o 1)) (set! n (- n 7))))
    (ly:make-pitch o n (/ a 4))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


Here's the D equivalent. Note that I've defined a basic object to represent a LilyPond pitch, which has the same characteristics as the pitch object used by the Scheme code. It's probably not how a pitch object would best be defined but is just there to illustrate.

I've also included a few of D's safety features with assert() commands to ensure that the variables have the properties required.

//////////////////////////////////////////////////////////////////
struct LilyPitch
{
    int octave;
    int notename;
    double alteration;

    pure this(int o, int n, double a)
    in
    {
        assert(0 <= n && n <= 6);
        assert(-1 <= a && a <= 1);
    }
    body
    {
        octave = o;
        notename = n;
        alteration = a;
    }
}

pure auto naturalizePitch(LilyPitch p)
{
    // Here we mimic what the Scheme function does and define separate
    // temporary variables to store the octave, note name and alteration.
    // We could just tweak p.octave, p.notename and p.alteration
    // directly, but I choose to assume that with a properly defined
    // LilyPitch we wouldn't have that possibility.
    auto o = p.octave;
    auto n = p.notename;
    auto a = p.alteration;

    // First, we disallow anything greater than +1/4-tone on E and B,
    // and anything less than -1/4-tone on C and F.  In other words,
    // no E# or B#, no Cb or Fb.
    if(a > 0.25 && (n == 6 || n == 2))
    {
        a -= 0.5;
        n += 1;
    }
    else if(a < -0.25 && (n == 0 || n == 3))
    {
        a += 0.5;
        n -= 1;
    }

    // Once that's dealt with, we disallow any accidental stronger than
    // a sharp or a flat, on ANY note.
    if(a > 0.5)
    {
        a -= 1.0;
        n += 1;
    }
    else if(a < -0.5)
    {
        a += 1.0;
        n -= 1;
    }

    // Last, we clean up the octave placement.
    if(n < 0)
    {
        o -= 1;
        n += 7;
    }
    else if(n > 6)
    {
        o += 1;
        n -= 7;
    }

    // Let's check that the note really falls within the
    // bounds we are looking for ...
    assert(0 <= n && n <= 6);
    assert(-0.5 <= a && a <= 0.5);
    if(n == 2 || n == 6)
        assert(a < 0.5);
    if(n == 0 || n == 3)
        assert(a > -0.5);

    return LilyPitch(o, n, a);
}
//////////////////////////////////////////////////////////////////

Now, I don't think the naturalize-pitch Scheme function is necessarily the best advertisement for Scheme I've ever seen, but I defy anyone to tell me that the D code is not clearer to follow, even without my added comments.

This is a bit of an unfair case because it's obviously a function where imperative programming is probably better suited. However, if you'd like to give me an example of a LilyPond function where Scheme allows for a much more elegant expression than C++, I will try and provide a D equivalent.

It's true that the Scheme example above is more concise, but only because in some cases multiple expressions have been squeezed onto a single line, whereas I've chosen to spread the D out for ease of reading. However, if you condensed the D, or expanded out the Scheme, I contend that _the D would still be easier to follow_, particularly for someone not versed in LISP. Let's see:

//////////////////////////////////////////////////////////////////
pure auto naturalizePitch(LilyPitch p)
{
    auto o = p.octave;
    auto n = p.notename;
    auto a = p.alteration;

    if(a > 0.25 && (n == 6 || n == 2)) { a -= 0.5; n += 1; }
    else if(a < -0.25 && (n == 0 || n == 3)) { a += 0.5; n -= 1; }

    if(a > 0.5) { a -= 1.0; n += 1; }
    else if(a < -0.5) { a += 1.0; n -= 1; }

    if(n < 0) { o -= 1; n += 7; }
    else if(n > 6) { o += 1; n -= 7; }

    assert(0 <= n && n <= 6);
    assert(-0.5 <= a && a <= 0.5);
    if(n == 2 || n == 6) assert(a < 0.5);
    if(n == 0 || n == 3) assert(a > -0.5);

    return LilyPitch(o, n, a);
}
//////////////////////////////////////////////////////////////////

A point I want to emphasize is that D allows you to have both low-level imperative programming and higher-level programming styles, with elegant syntax in both cases. So whereas in another case you might have to use a high-level language for the general program architecture and C/C++ to build highly efficient individual components, in D you can do both within the same language.

Regarding new languages, while I don't want to re-open the "alphabet
soup" discussion, my suggestion wasn't simply a casual shout-out to a
cool new language; it was a carefully-considered proposal based on
concerns for programming power, ease and flexibility of syntax, code
efficiency and suitability for the next generation of hardware.

Fewer buzzphrases, more substance.

OK.  Here's some of what I see in D:

  * Basic syntax is close to C/C++/Java/C# making it easy to adapt to from
    any of these widely-used languages.

  * Compiles down to fast native code with performance virtually identical to
    C/C++, but can also be run in scripting mode.

  * Fast to compile (faster than Go IIRC, _much_ faster than C++).

  * Automatic garbage collection, but this can be disabled if needed.

  * Inbuilt support for functional programming, enforcement of purity etc.
    but with a tolerant approach that allows mutation of internal functional
    variables as long as it has no side-effects (e.g. my naturalizePitch is
    a pure function, even though the way it's written it has mutation of
    internal variables).

  * Thread-safe globals by default, inbuilt support for immutable variables,
    inbuilt support for both message passing and mutex-based approaches to
    concurrency.

  * Currently a small but very enthusiastic developer and user community, but
    picking up a growing interest from games developers, scientific programmers,
    and others who have strong joint requirements for speed, safety and ease of
    writing/debugging.  It's also clear there is interest from Facebook and
    other organizations that have to write large-scale web applications with low
    per-user energy cost.

... and other stuff; but that's enough for now, I think.



reply via email to

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