Professional Web Applications Themes

Thoughts on yield - Ruby

I've begun working on a music-related ruby project, and recently I've been pondering the idea of a music composition environment somewhat similar to cm, clm and friends for lisp, but in ruby instead. Today I was thinking about how ruby blocks make constructing domain-specific languages a snap. I've been reading Lisp as a Second Language at [url]http://www.ircam.fr/equipes/repmus/LispSecondLanguage/[/url], a Lisp tutorial for musicians, and was considering using a list-based structure for storing music. I sorta envisioned the language as looking something like: clef do measure("4/4") do c d e f g a b c chord do c e g end end ...

  1. #1

    Default Thoughts on yield

    I've begun working on a music-related ruby project, and recently I've
    been pondering the idea of a music composition environment somewhat
    similar to cm, clm and friends for lisp, but in ruby instead.

    Today I was thinking about how ruby blocks make constructing
    domain-specific languages a snap. I've been reading Lisp as a Second
    Language at [url]http://www.ircam.fr/equipes/repmus/LispSecondLanguage/[/url], a
    Lisp tutorial for musicians, and was considering using a list-based
    structure for storing music. I sorta envisioned the language as
    looking something like:

    clef do
    measure("4/4") do
    c d e f g a b c
    chord do
    c e g
    end
    end
    end

    In this example, a-g are methods returning note objects.

    What I'd like is to have that return a list like so:

    ["c", "d", "e", "f", "g", "a", "b", "c", ["c", "e", "g"]]

    But, as it stands, each expression is evaluated and discarded. I'd
    like to avoid having to include actual lists in the blocks; while
    arbitrary ruby expressions would be possible, I'm aiming to hide much
    of that from the casual user.

    So, my question. Is there any way to achieve something like that using
    blocks? Is there any way to have yield do something other than run the
    given code?

    Assuming not, I was wondering how practical/horrible it would be to
    have yield accept an optional block which, when given one parameter,
    sets that parameter to the value of every top-level expression run
    within the block? So, for instance, if I wanted to evaluate all of the
    above expressions and concatenate their results into a list, I could
    do:

    score = []
    yield { |x| score += [x] }

    Of course, I realize that probably breaks the principle of least
    surprise in a few instances, as many would expect the contents of a
    block to be executed and only the result of all code returned, but in
    my rather humble and untrained opinion, this would be useful for
    implementing domain-specific languages.

    Or is there a better way to accomplish what I'm trying?
    Nolan J. Darilek Guest

  2. #2

    Default Re: Thoughts on yield

    In article <87brt5wz83.fsfethereal.dyndns.org>,
    Nolan J. Darilek <nolan_dbigfoot.com> wrote:
    >I've begun working on a music-related ruby project, and recently I've
    >been pondering the idea of a music composition environment somewhat
    >similar to cm, clm and friends for lisp, but in ruby instead.
    >
    >Today I was thinking about how ruby blocks make constructing
    >domain-specific languages a snap. I've been reading Lisp as a Second
    >Language at [url]http://www.ircam.fr/equipes/repmus/LispSecondLanguage/[/url], a
    >Lisp tutorial for musicians, and was considering using a list-based
    >structure for storing music. I sorta envisioned the language as
    >looking something like:
    >
    >clef do
    > measure("4/4") do
    > c d e f g a b c
    > chord do
    > c e g
    > end
    > end
    >end
    >
    >In this example, a-g are methods returning note objects.
    >
    >What I'd like is to have that return a list like so:
    >
    >["c", "d", "e", "f", "g", "a", "b", "c", ["c", "e", "g"]]
    >

    I played with this problem for a few minutes and this is what I came up
    with:

    module Music

    def a(list=notes)
    list << "a"
    end

    def b(list=notes)
    list << "b"
    end

    def c(list=notes)
    list << "c"
    end

    def d(list=notes)
    list << "d"
    end

    def e(list=notes)
    list << "e"
    end

    def f(list=notes)
    list << "f"
    end

    def g(list=notes)
    list << "g"
    end

    def clef
    puts "...In Clef..."
    yield
    return notes
    end

    def measure(time)
    puts "...In Measure..."
    yield
    end

    def chord
    puts "...In Chord..."
    yield chord_notes
    notes << chord_notes
    end

    def notes
    notes
    end
    end

    class Song
    include Music
    def initialize
    notes = []
    chord_notes=[]
    end
    end

    class MySong < Song
    include Music
    def initialize
    super
    clef do
    measure("4/4") do
    c; d; e; f; g; a; b; c;
    chord do |chrd|
    c(chrd); d(chrd); g(chrd);
    end
    end
    end
    end
    end

    mysong = MySong.new
    p mysong.notes

    Of course, the ugly bit is the part in the chord block: you have to
    pass in 'chrd' to all of the a-g methods to get those notes to be added to
    the correct list (chord_list) which then gets appended to the notes
    list. So it's still not quite what you want (I understand that you want
    people to see very little of the 'wizard behind the curtain' [Ruby] and
    having to pass 'chrd' sort of negates that). Hopefully, someone else
    will offer a suggestion for getting rid of that requirement.

    ....you may not like the Song class bit I added either...

    Phil
    Phil Tomson Guest

  3. #3

    Default Re: Thoughts on yield

    In article <87brt5wz83.fsfethereal.dyndns.org>,
    Nolan J. Darilek <nolan_dbigfoot.com> wrote:
    >I've begun working on a music-related ruby project, and recently I've
    >been pondering the idea of a music composition environment somewhat
    >similar to cm, clm and friends for lisp, but in ruby instead.
    >
    >Today I was thinking about how ruby blocks make constructing
    >domain-specific languages a snap. I've been reading Lisp as a Second
    >Language at [url]http://www.ircam.fr/equipes/repmus/LispSecondLanguage/[/url], a
    >Lisp tutorial for musicians, and was considering using a list-based
    >structure for storing music. I sorta envisioned the language as
    >looking something like:
    >
    >clef do
    > measure("4/4") do
    > c d e f g a b c
    > chord do
    > c e g
    > end
    > end
    >end
    >
    >In this example, a-g are methods returning note objects.
    >
    >What I'd like is to have that return a list like so:
    >
    >["c", "d", "e", "f", "g", "a", "b", "c", ["c", "e", "g"]]
    >
    I did a little more work on this; now I don't have to pass in a list to
    the notes from within the chord block:

    module Music

    def a(list=tmp_notes)
    list << "a"
    end

    def b(list=tmp_notes)
    list << "b"
    end

    def c(list=tmp_notes)
    list << "c"
    end

    def d(list=tmp_notes)
    list << "d"
    end

    def e(list=tmp_notes)
    list << "e"
    end

    def f(list=tmp_notes)
    list << "f"
    end

    def g(list=tmp_notes)
    list << "g"
    end

    def clef
    puts "...In Clef..."
    yield
    notes = tmp_notes
    notes << chord_notes unless chord_notes.empty?
    return notes
    end

    def measure(time)
    puts "...In Measure..."
    yield
    end

    def chord
    #make a local copy of tmp_notes
    tmpnotes = tmp_notes.dup
    tmp_notes.clear
    puts "...In Chord..."
    yield chord_notes
    chord_notes = tmp_notes
    #restore tmp_notes
    tmp_notes = tmpnotes
    end

    def notes
    notes
    end
    end

    class Song
    include Music
    def initialize
    notes = []
    chord_notes=[]
    tmp_notes = []
    end
    end

    class MySong < Song
    include Music
    def initialize
    super
    clef do
    measure("4/4") do
    c; d; e; f; g; a; b; c;
    chord do
    c; d; g;
    end
    end
    end
    end
    end

    mysong = MySong.new
    p mysong.notes


    So that gets rid of some of the ugliness that the user of your music
    language has to know (you want them to not have to know Ruby at all), but
    the super is still there.

    Perhaps you could do something like this instead of subclassing Song, you
    could do (untested):

    Defined by you:
    class Song
    include Music
    def initialize
    notes = []
    chord_notes=[]
    tmp_notes = []
    yield
    end
    end


    Defined by user in a different file:

    my_song= Song.new {
    clef do
    measure("4/4") do
    c; d; e; f; g; a; b; c;
    chord do
    c; d; g;
    end
    end
    end
    }

    The advantage being that you can eliminate the need for the user to have
    to define a class and call super. You could even add more methods to
    either Song or Music so that you could do:

    my_song.play

    ....but I'm not sure that will work; the block being passed to Song.new has
    to know about all of the methods we're using in it (a-f refer to
    temp_list which doesn't have a context in this block). There's probably
    some way of doing this...

    Phil


    Phil Tomson Guest

  4. #4

    Default Re: Thoughts on yield

    On Sunday, 28 September 2003 at 9:30:15 +0900, Nolan J. Darilek wrote:
    >
    > clef do
    > measure("4/4") do
    > c d e f g a b c
    > chord do
    > c e g
    > end
    > end
    > end
    >
    > In this example, a-g are methods returning note objects.
    >
    > What I'd like is to have that return a list like so:
    >
    > ["c", "d", "e", "f", "g", "a", "b", "c", ["c", "e", "g"]]
    >
    > But, as it stands, each expression is evaluated and discarded. I'd
    > like to avoid having to include actual lists in the blocks; while
    > arbitrary ruby expressions would be possible, I'm aiming to hide much
    > of that from the casual user.
    >
    At the basic level do you want

    chord { c e g }

    to return ["c", "e", "g"]?

    It seems that you want to generate a list
    using a lisp syntax. And to do that inside
    a block, you probably need a hook to catch
    the return value of each statment.
    I'm not sure that would be advisable.

    Have you considered parsing a string and using eval?
    For example, do:

    chord %{ c e g }


    Of course, some smarter people than me may provide
    the answer you are looking for.

    --
    Jim Freeze
    ----------
    A celebrity is a person who is known for his well-knownness.

    Jim Freeze Guest

  5. #5

    Default Re: Thoughts on yield

    Phil Tomson wrote:
    > Of course, the ugly bit is the part in the chord block: you have to
    > pass in 'chrd' to all of the a-g methods to get those notes to be added to
    > the correct list (chord_list) which then gets appended to the notes
    > list. So it's still not quite what you want (I understand that you want
    > people to see very little of the 'wizard behind the curtain' [Ruby] and
    > having to pass 'chrd' sort of negates that). Hopefully, someone else
    > will offer a suggestion for getting rid of that requirement.
    I suggest our old friend instance_eval.

    --

    diff -u music-orig.rb music.rb
    --- music-orig.rb 2003-09-27 22:04:11.000000000 -0700
    +++ music.rb 2003-09-27 22:03:43.000000000 -0700
    -41,8 +41,9

    def chord
    puts "...In Chord..."
    - yield chord_notes
    - notes << chord_notes
    + chord_notes.instance_eval &Proc.new
    + notes << chord_notes.notes.dup
    + chord_notes.clear
    end

    def notes
    -50,23 +51,32
    end
    end

    +class Chord
    + include Music
    + def initialize
    + notes = []
    + end
    + def clear
    + notes.clear
    + end
    +end
    +
    class Song
    include Music
    def initialize
    notes = []
    - chord_notes=[]
    + chord_notes=Chord.new
    end
    end

    class MySong < Song
    - include Music
    def initialize
    super
    clef do
    measure("4/4") do
    c; d; e; f; g; a; b; c;
    - chord do |chrd|
    - c(chrd); d(chrd); g(chrd);
    + chord do
    + c; d; g;
    end
    end
    end


    Joel VanderWerf Guest

  6. #6

    Default Re: Thoughts on yield


    What about an algebraic approach?

    Instead of

    clef do
    measure("4/4") do
    c; d; e; f; g; a; b; c;
    chord do
    c; d; g;
    end
    end
    end

    maybe

    clef do
    measure("4/4") do
    c + d + e + f + g + a + b + c + c*d*g
    end
    end

    You could denote quarter notes with b/4 etc.


    Joel VanderWerf Guest

  7. #7

    Default Re: Thoughts on yield


    Here's take 3. This one seems to work:

    module Music
    def initialize
    notes = []
    chord_notes=[]
    tmp_notes = []
    end

    def a(list=tmp_notes)
    list << "a"
    end

    def b(list=tmp_notes)
    list << "b"
    end

    def c(list=tmp_notes)
    list << "c"
    end

    def d(list=tmp_notes)
    list << "d"
    end

    def e(list=tmp_notes)
    list << "e"
    end

    def f(list=tmp_notes)
    list << "f"
    end

    def g(list=tmp_notes)
    list << "g"
    end

    def clef
    puts "...In Clef..."
    yield
    notes = tmp_notes
    notes << chord_notes unless chord_notes.empty?
    return notes
    end

    def measure(time)
    puts "...In Measure..."
    yield
    end

    def chord
    tmpnotes = tmp_notes.dup
    tmp_notes.clear
    puts "...In Chord..."
    yield chord_notes
    chord_notes = tmp_notes
    tmp_notes = tmpnotes
    end

    def notes
    notes
    end
    end

    class Song
    include Music

    def define_song(&b)
    instance_eval &b
    end
    end

    mysong= Song.new
    mysong.define_song {
    clef do
    measure("4/4") do
    c; d; e; f; g; a; b; c;
    chord do
    c; d; g;
    end
    end
    end
    }
    p mysong.notes


    This way the user doesn't even have to define a class, call super and all
    those Ruby details.


    Thanks to Joel for the instance_eval suggestion.

    Phil
    Phil Tomson Guest

  8. #8

    Default Re: Thoughts on yield


    "Jim Freeze" <jimfreeze.org> wrote:
    > Nolan J. Darilek wrote:
    > >
    >
    > Have you considered parsing a string ...
    > For example, do:
    >
    > chord %{ c e g }
    >

    Like Jim, my feeling is that some parsing will be
    required. There's no C# method in Ruby.

    Allows for sharps, flats and naturals.

    My effort was also helped by Joel's input.

    Unique Note objects are created for no reason other
    than folks (and my) amusement.


    #====================

    module Music

    class Clef
    def initialize
    notes = []
    end

    def time_sig(beats, beatval)
    beats = beats
    beatval = beatval
    end

    def chord(c)
    notes << c.split.map {|ce| Note[ce] }
    end

    def notes(n=nil)
    if n
    notes << n.split.map {|ne| Note[ne] }
    else
    notes
    end
    end

    def inspect
    n
    end
    end

    class Note
    private_class_method :new
    note_h = Hash[]

    def Note.[](n)
    puts "Bum Note - #{n}" if n !~ /[a-g][#_]?/i
    nn = note_h[n] || note_h[n] = new(n)
    end

    def initialize(n)
    n = n
    end

    def inspect
    n
    end
    end

    def clef(&b)
    clf = Clef.new
    clf.instance_eval(&b)
    r = clf.notes
    p r
    r
    end
    end

    #====================

    include Music

    clef do
    time_sig(4,4)
    notes %{d e f# g a b c# d}
    chord %{c d g}
    end

    clef do
    time_sig(3,4)
    notes %{a_ b c# d}
    chord %{d k}
    end

    #====================

    #-> [[d, e, f#, g, a, b, c#, d], [c, d, g]]
    #-> Bum Note - k
    #-> [[a_, b, c#, d], [d, k]]


    daz




    daz Guest

  9. #9

    Default Re: Thoughts on yield


    I wrote:
    >
    > def chord(c)
    > notes << c.split.map {|ce| Note[ce] } # OK
    > end
    >
    > def notes(n=nil)
    > if n
    > notes << n.split.map {|ne| Note[ne] } # Oops !!
    # ^^

    notes += n.split.map {|ne| Note[ne] }
    > else
    > notes
    > end
    > end
    >
    > #====================
    >
    Oops, I pushed the note array instead of joining.
    Don't want people to think I don't care ;-)

    #-> [d, e, f#, g, a, b, c#, d, [c, d, g]]


    daz



    daz Guest

  10. #10

    Default Re: Thoughts on yield

    On Sun, 28 Sep 2003 17:31:23 +0900
    "daz" <doobyd10.karoo.co.uk> wrote:
    > Like Jim, my feeling is that some parsing will be
    > required. There's no C# method in Ruby.
    >
    > Allows for sharps, flats and naturals.
    There are however unary +, -, and ~ operators, which might translate
    appropriately.

    However there are a lot of intricacies to music in general, and I'm
    not sure any of the given methods quite handle them all. Some
    everyday things to think about:

    * Tuples
    * Multiple voices (not just simple chords)
    * Note length tweaks (for slurs and the like)
    * Rests (big one)
    * Tempo changes
    * Dynamics

    Arrays might help some things, as well as combining voices on the fly,
    but it may not be as straightforward as desired.

    Hmm, a little method_missing magic and we could have a vaguely
    track-like view perhaps:

    part {
    bar 16 # Set each _ to be a 16th, so ____ is 1/4

    v1 a____, r_, ...
    v2 r_, b__, +c__, ... # r is for rest

    v1 ...
    v2 ...
    }

    In this case, you could chop off the _'s and count them in
    method_missing to produce a length, call the method with that as a
    parameter, returning a Note modified by the current key signature, and
    then the +/-/~ would take over. Each vN function would be a "voice"
    function that was later collected and combined by part(), so you could
    handle multiple voice movement without a lot of crazy arrays and
    stuff. Of course, you'd likely still want to allow [a_, +c_, e_] to
    give you a chord in a particular voice for convenience.

    Anyhow, just a thought, still a number of issues to be dealt with I'm
    sure.

    --
    Ryan Pavlik <rpavmephle.com>

    "It's at the expense of some of my favorite organs,
    but it's worth it." - 8BT

    Ryan Pavlik Guest

  11. #11

    Default Re: Thoughts on yield

    [email]nolan_dbigfoot.com[/email] (Nolan J. Darilek) wrote:
    > I sorta envisioned the language as looking something like:
    >
    > clef do
    > measure("4/4") do
    > c d e f g a b c
    > chord do
    > c e g
    > end
    > end
    > end
    >
    > In this example, a-g are methods returning note objects.
    >
    > What I'd like is to have that return a list like so:
    >
    > ["c", "d", "e", "f", "g", "a", "b", "c", ["c", "e", "g"]]
    Possible. But not for long.

    #!/usr/bin/ruby -w

    def clef
    yield
    end

    def measure(m)
    lines = []
    yield
    lines.inject {|a,b| a.push(*b)}
    end

    def chord
    lines[-1] = [yield]
    end

    ('a'..'g').each {|x| eval <<""}
    def #{x}(*a)
    lines << [] if a.empty?
    lines[-1].unshift('#{x}')
    end

    notes = clef do
    measure("4/4") do
    c d e f g a b c
    chord do
    c e g
    end
    end
    end

    p notes

    __END__
    ../gem:25: warning: parenthesize argument(s) for future version
    ../gem:25: warning: parenthesize argument(s) for future version
    ../gem:25: warning: parenthesize argument(s) for future version
    ../gem:25: warning: parenthesize argument(s) for future version
    ../gem:25: warning: parenthesize argument(s) for future version
    ../gem:25: warning: parenthesize argument(s) for future version
    ../gem:27: warning: parenthesize argument(s) for future version
    ["c", "d", "e", "f", "g", "a", "b", "c", ["c", "e", "g"]]
    Sabby and Tabby Guest

  12. #12

    Default Re: Thoughts on yield



    On Sun, 2003-09-28 at 17:12, Joel VanderWerf wrote:
    > What about an algebraic approach?
    Kool idea. I like it.
    > maybe
    >
    > clef do
    > measure("4/4") do
    > c + d + e + f + g + a + b + c + c*d*g
    > end
    > end
    >
    > You could denote quarter notes with b/4 etc.
    >

    Paul William Guest

  13. #13

    Default Re: Thoughts on yield

    Sabby and Tabby wrote:
    >('a'..'g').each {|x| eval <<""}
    >def #{x}(*a)
    > lines << [] if a.empty?
    > lines[-1].unshift('#{x}')
    >end
    >
    >
    I didn't realize that the first line didn't count for here doents, so you
    could close blocks and such. I thought you would have needed:

    ('a'..'g').each {|x| eval <<""
    def #{x}(*a)
    lines << [] if a.empty?
    lines[-1].unshift('#{x}')
    end

    }

    You learn something new every day, I guess.

    - Dan


    Dan Doel Guest

  14. #14

    Default Re: Thoughts on yield

    Dan Doel wrote:
    > Sabby and Tabby wrote:
    >
    >> ('a'..'g').each {|x| eval <<""}
    >> def #{x}(*a)
    >> lines << [] if a.empty?
    >> lines[-1].unshift('#{x}')
    >> end
    >>
    >>
    > I didn't realize that the first line didn't count for here doents, so
    > you
    > could close blocks and such. I thought you would have needed:
    >
    > ('a'..'g').each {|x| eval <<""
    > def #{x}(*a)
    > lines << [] if a.empty?
    > lines[-1].unshift('#{x}')
    > end
    >
    > }
    >
    > You learn something new every day, I guess.
    What? I'm confused. Are you saying that there are
    two blank lines required at the end of this doc?

    Personally I've never used "" as a terminator
    anyway.

    Hal


    Hal Fulton Guest

  15. #15

    Default Re: Thoughts on yield

    Hal Fulton wrote:
    > What? I'm confused. Are you saying that there are
    > two blank lines required at the end of this doc?
    >
    > Personally I've never used "" as a terminator
    > anyway.
    >
    No, just one. Anything more is Thunderbird making it hard for me to format
    messages precisely.

    - Dan


    Dan Doel Guest

  16. #16

    Default Re: Thoughts on yield

    Sabby and Tabby <sabbyxtabby> wrote:
    >
    > Possible. But not for long.
    >
    > #!/usr/bin/ruby -w
    >
    > def clef
    > yield
    > end
    >
    > def measure(m)
    > lines = []
    > yield
    > lines.inject {|a,b| a.push(*b)}
    > end
    >
    > def chord
    > lines[-1] = [yield]
    > end
    >
    > ('a'..'g').each {|x| eval <<""}
    > def #{x}(*a)
    > lines << [] if a.empty?
    > lines[-1].unshift('#{x}')
    > end
    >
    > notes = clef do
    > measure("4/4") do
    > c d e f g a b c
    > chord do
    > c e g
    > end
    > end
    > end
    >
    > p notes
    Beautiful. Pity it's going away.

    martin
    Martin DeMello Guest

  17. #17

    Default Re: Thoughts on yield

    Martin DeMello wrote:
    > Sabby and Tabby <sabbyxtabby> wrote:
    >
    >>Possible. But not for long.
    >>notes = clef do
    >> measure("4/4") do
    >> c d e f g a b c
    >> chord do
    >> c e g
    >> end
    >> end
    >>end
    >>
    >>p notes
    >
    >
    > Beautiful. Pity it's going away.
    I agree. But "poetry mode" is not totally going away,
    is it? Don't we just get the warning in certain
    cirstances?

    What are the facts here?

    Hal


    Hal Fulton Guest

Similar Threads

  1. break in yield/block
    By matt in forum Ruby
    Replies: 7
    Last Post: November 21st, 08:56 PM
  2. Replies: 6
    Last Post: July 17th, 01:19 PM

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139