Proc / def / yield semantics (long)

Discussion in 'Ruby' started by Markus, Sep 25, 2004.

  1. Markus

    Markus Guest

    ABSTRACT

    The semantics of Proc object calling was changed between 1.8.0 and
    1.8.1 to resolve a disparity with the semantics of yield(); I feel this
    was a mistake & would like to see the change reversed. Specifically:

    * For 1.8.2 I would like to see the semantics revert to the way
    they were in 1.8.0; this leaves the disparity between yield and
    Proc#call, but does not break anything.

    * For 2.0 I would like the semantics to be homogenized, either by:

    * Changing yield (and assignment) to match the semantics
    of the rest of the language,
    or
    * Making the behavior controllable by the user (on a case
    by case basis) so that people who want the new behavior
    can get it.


    BACKGROUND

    Ruby supports parallel assignment and array expansion with
    relatively clean semantics. Arrays used as the last rvalue or the last
    lvalue can be expanded by prefixing them with an "*"; the only
    inconsistency come from the fact that:

    This is not necessary if the rvalue is the only thing on the
    right hand side--the array will be expanded automatically.
    -- (Pickaxe I, page 77)

    This syntactic "shortcut" can lead to some decidedly unobvious
    behavior. For example, adding a rvalue to the right hand side of an
    assignment statement can cause the class of any of the other lvalues to
    change, but only under some conditions:

    x = [1]

    a,b = x
    p a.class #Fixnum

    a,b = x,4
    p a.class #Array

    x = "1"

    a,b = x
    p a.class #String

    a,b = x,4
    p a.class #String

    In 1.8.0, only assignment and yield, of the half dozen or so ways
    to assign values to a collection of variables, special case single
    rvalues which happen to be arrays this way. (The code in listing one
    can be used to test the behavior of the different version.)

    In the case of assignment, the "feature" is of very slight utility,
    since the same effect can be had (if desired) by prefixing the single
    rvalue with an "*".

    Where it is must useful is in the case of yield, where it
    facilitates constructs such as:

    my_hash.each { |key,value| ... }

    which are widely used.

    THE CHANGE

    A consequence of this is that using yield on a block has different
    semantics than converting the block to a Proc and calling it (as noted
    in http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/21726):

    def foo
    yield([:key, :value])
    end
    foo {|k, v| p [k, v]} #[:key, :value]

    def bar(&blk)
    blk.call([:key, :value])
    end
    bar {|k, v| p [k, v]} #[[:key, :value], nil]


    I am somewhat hampered by my illiteracy (at best I can "read"
    Japanese at a rate of 1 page a month or so, given sufficient reference
    materials) and therefore I am guessing in what follows.

    There are two ways that the discrepancy could be resolved: either
    the behavior of yield could be changed, or that of Proc#call. If the
    former path had been taken, we would have:


    def foo
    yield([:key, :value])
    end
    foo {|k, v| p [k, v]} #[[:key, :value], nil]

    def bar(&blk)
    blk.call([:key, :value])
    end
    bar {|k, v| p [k, v]} #[[:key, :value], nil]

    and the prior behavior could have been reproduced (if desired) by writing:

    def foo
    yield(*[:key, :value])
    end
    foo {|k, v| p [k, v]} #[:key, :value]

    or more simply (in the example case at least):

    def foo
    yield:)key, :value)
    end
    foo {|k, v| p [k, v]} #[:key, :value]

    (This is the choice I would have advocated had I been following the
    debate. But I wasn't and so I am making this 11th hour plea.)

    Instead, it was decided to change Proc. This, I surmise, led to
    some problems and a compromise appears to have been hammered out along
    these lines (using the tags from the program in listing 1):

    inline: special case single arrays
    inline (array): no special casing
    def: no special casing
    Proc.new: special case single arrays
    proc: no special casing
    My_proc.new: special case single arrays
    my_proc: special case single arrays
    yield: special case single arrays

    Contrast this with the situation in 1.8.0:

    inline: special case single arrays
    inline (array): no special casing
    def: no special casing
    Proc.new: no special casing
    proc: no special casing
    My_proc.new: no special casing
    my_proc: no special casing
    yield: special case single arrays

    and you will note that:

    * Kernel#proc is now almost but not quite a synonym for Proc.new

    * It does not appear to be possible to subclass Proc without
    getting the special case behavior

    * It does not appear to be possible to write "wrapper" Procs (a'la
    CLOS) that act like methods

    * It does not appear to be possible to define something that works
    like Kernel#proc

    * The potential for odd behavior noted in the background section
    has not gone away; instead, it has spread to many more contexts.

    CONSEQUENCES

    There is a venerable pattern (dating back decades before we even
    called them patterns) for dealing with sequences recursively by at each
    stage treating the list as a pair: the first item and the rest of the
    list. (CAR/CDR)

    Up until 1.8.0 (and, I hope, in 1.8.2 on) it is easy to implement
    this pattern in ruby. For example:

    def tickle(head,*rest)
    head.giggle if head.respond_to? :giggle
    tickle(rest) if rest
    end

    This works, but breaks subtly under the new semantics if you try to
    make it an object:

    tickeler = Proc.new { |head,*rest|
    head.giggle if head.respond_to? :giggle
    self.call(rest) if rest
    }


    Another useful construct that is possible with the old semantics
    but not with the new is before methods (borrowed from CLOS):

    $observers = {}
    class Module
    def before(method,&block)
    new_name = Symbol.unique(method)
    alias_method new_name, method
    block_name = Symbol.unique(method)
    $observers[block_name] = block
    module_eval %{
    def #{method}(*args, &block)
    $observers[:#{block_name}].
    call(self,*args,&block)
    #{new_name}(*args,&block)
    end
    }
    return [self, method, new_name, block_name]
    end
    end

    with which we can write:

    class Animal
    def dinner(...)
    :
    end
    :
    end

    Animal.before :dinner do |animal,*args|
    :
    end


    Of course, this only works so long as the semantics of calling a
    block are the same as the semantics of calling a method. In other
    words, it works fine under 1.8.0 but fails mysteriously under 1.8.1

    There are many such examples, but most of them are much harder to
    abstract (or at least, harder to abstract concisely). For example, the
    Action pattern and its kin are much easier to implement if Procs and
    methods don't have different special cases.


    PROPOSED RESOLUTION

    I gather the deadline for 1.8.2 is looming. Therefore, for 1.8.2,
    I would propose going back to the 1.8.0 semantics. This could be done
    (I believe) by simply reversing the change made by Matz on 30 September
    2003. Anyone who wants the new behavior should be able to get it by the
    judicious use of "*".

    For 2.0 (which, as I understand it, is not committed to 100%
    backward compatibility), I would recommend one of the following:

    * If no one can devise a case in which it would not be possible to
    reproduce the special case behavior by prefixing single rvalues
    with an "*", I would recommend eliminating the special case
    altogether. This would mean changing the semantics of yield and
    assignment slightly, through I would be surprised if there was
    much code at all (out side of iterators over structures such as
    hashes) that _wants_ the new behavior.

    * If there is some reason why yield and assignment need to keep
    the special case, I would like to see a flag added to the Proc
    object that determines its behavior. Rather than having
    Kernal#proc return one type of Proc and Proc#new return a subtly
    different type of Proc, both should return the same thing and
    its behavior should be adjustable either at creation time (via a
    parameter to the creation method) or by sending a message to the
    object (Proc#expand_array_parameters=, or some such).



    Thank you for taking time to read this; I'm sorry I wasn't alert
    enough to offer it a year ago. As always, I am more than willing to
    help in whatever way is needed.

    -- Markus



    LISTING 1

    def show(h,r)
    print " head: #{h.inspect}, rest: #{r.inspect} "
    end


    #-----------------------------------------------
    print "inline: "
    #head,*rest = 1,2,3 ; show(head,rest)
    #head,*rest = [1],2,3 ; show(head,rest)
    head,*rest = [1,2],3 ; show(head,rest)
    head,*rest = [1,2,3] ; show(head,rest)
    print "\n"

    #-----------------------------------------------
    print "inline (array): "
    #a = [1,2,3] ; h,*r = *a; show(head,rest)
    #a = [[1],2,3] ; h,*r = *a; show(head,rest)
    a = [[1,2],3] ; h,*r = *a; show(head,rest)
    a = [[1,2,3]] ; h,*r = *a; show(head,rest)
    print "\n"

    #-----------------------------------------------
    print "def: "
    def test1(head,*rest)
    show(head,rest)
    end
    #test1(1,2,3)
    #test1([1],2,3)
    test1([1,2],3)
    test1([1,2,3])
    print "\n"

    #-----------------------------------------------
    print "Proc.new: "
    test2 = Proc.new { |head,*rest|
    show(head,rest)
    }
    #test2.call(1,2,3)
    #test2.call([1],2,3)
    test2.call([1,2],3)
    test2.call([1,2,3])
    print "\n"

    #-----------------------------------------------
    print "proc: "
    test3 = proc { |head,*rest|
    show(head,rest)
    }
    #test3.call(1,2,3)
    #test3.call([1],2,3)
    test3.call([1,2],3)
    test3.call([1,2,3])
    print "\n"

    #-----------------------------------------------
    print "My_proc.new: "
    class My_proc < Proc
    end
    test4 = My_proc.new { |head,*rest|
    show(head,rest)
    }
    #test4.call(1,2,3)
    #test4.call([1],2,3)
    test4.call([1,2],3)
    test4.call([1,2,3])
    print "\n"

    #-----------------------------------------------
    print "my_proc: "
    def my_proc(&b)
    b
    end
    test5 = my_proc { |head,*rest|
    show(head,rest)
    }
    #test5.call(1,2,3)
    #test5.call([1],2,3)
    test5.call([1,2],3)
    test5.call([1,2,3])
    print "\n"

    #-----------------------------------------------
    print "yield: "
    def test6
    #yield(1,2,3)
    #yield([1],2,3)
    yield([1,2],3)
    yield([1,2,3])
    end
    test6 { |head,*rest|
    show(head,rest)
    }
    print "\n"
    Markus, Sep 25, 2004
    #1
    1. Advertising

  2. Markus

    ts Guest

    >>>>> "M" == Markus <> writes:

    M> * It does not appear to be possible to subclass Proc without
    M> getting the special case behavior

    svg% cat b.rb
    #!/usr/bin/ruby
    class A < Proc
    def self.new(&block)
    proc(&block)
    end
    end

    a = A.new {|a, *b| p a, b }
    a.call([1, 2, 3])
    svg%

    svg% b.rb
    [1, 2, 3]
    []
    svg%



    Guy Decoux
    ts, Sep 25, 2004
    #2
    1. Advertising

  3. I support all that you have written, as I have been bitten by this
    inconsistency in the past (in a less serious way than you).

    Can I add some simple observations:

    (1) When you write a,b = c

    then Ruby has to assume you mean either:

    (i) a,b = c,nil
    or (ii) a,b = *c

    In the simple assignment case, both can be achieved explicitly; I guess Ruby
    chooses (ii) as "more likely to be what you meant", but you don't actually
    need that magic because you can achieve it youself using "*". Similarly for
    a = b,c:

    (iii) a,dummy = b,c
    (iv) *a = b,c

    (2) The problem with Hash#each is that for hashes to be enumerable they must
    contain a list of individual "things", and a "thing" is chosen here to be a
    [key,value] pair.

    So Hash#each does yield [k,v]

    and the block we pass in takes |thing| as a parameter. Now, we'd like to be
    able to do
    myhash.each { |key,value| ... }

    but we need magic for that. We can't achieve it using "*", because we'd have
    to change the 'yield' statement to do that: yield *[k,v], or yield k,v. In
    other words, we'd have to modify the source code of the iterator itself.

    NOW: if it's *only* Hash#each which is the problem here, then one Ruby-2.0
    solution is to add a new iterator to Hash, or just redefine

    Hash#each_pair --> yield k,v

    This iterator already exists in the language, so you could make your code
    backwards-compatibile by using it where you want to do { |key,value| ... }

    But it seems to me a better option would be to have a way at the *receiver*
    of specifying that you want the argument 'exploded', and it turns out that
    Ruby already has this:

    myhash.each { |(key,value)| ... }

    Tested in 1.8.1:

    def foo
    yield [1,2], [3,4]
    end

    foo { |a,b| p a,b }
    [1, 2]
    [3, 4]
    => nil

    foo { |x,(y,z)| p x,y,z }
    [1, 2]
    3
    4
    => nil

    So perhaps all we need to do, for ruby-2.0 at least, is keep this mechanism
    and get rid of the rule which says "if single argument is an array then
    auto-expand it" completely.

    And then, maybe we should persuade people to write
    (a,b)=c
    rather than
    a,b=*c

    (although exploding at the sender, i.e. "yield *foo" and "bar(*foo)", are
    still useful constructs)

    Applying this to your example:

    > There are two ways that the discrepancy could be resolved: either
    > the behavior of yield could be changed, or that of Proc#call. If the
    > former path had been taken

    ...
    > the prior behavior could have been reproduced (if desired) by writing:
    >
    > def foo
    > yield(*[:key, :value])
    > end
    > foo {|k, v| p [k, v]} #[:key, :value]


    Without changing the definition of foo, you could just have called it as

    foo {|(k,v)| p [k, v]} #[:key, :value]


    (3) It may be possible to keep Ruby's automagic exploding of arguments, if
    we limit it to cases where there is no ambiguity. I am thinking of:

    - the sender passes a single array value;
    - the receiver REQUIRES two or more arguments.

    This is unlike def foo(head,*rest) or proc {|head,*rest| ... }, where it can
    take _one_ or more arguments, so the rule would not apply.

    This might be a solution for 1.8.2, where you want to be backwards
    compatible. At least, Hash#each { |key,value| ... } would continue to work.
    But it still wouldn't be consistent, unless you also applied it also to
    method calls (which would be dubious, IMO)

    Regards,

    Brian.
    Brian Candler, Sep 25, 2004
    #3
  4. Kudos.

    On Saturday 25 September 2004 12:58 am, Markus wrote:
    > ABSTRACT
    >
    > The semantics of Proc object calling was changed between 1.8.0 and
    > 1.8.1 to resolve a disparity with the semantics of yield(); I feel this
    > was a mistake & would like to see the change reversed. Specifically:
    >
    > * For 1.8.2 I would like to see the semantics revert to the way
    > they were in 1.8.0; this leaves the disparity between yield and
    > Proc#call, but does not break anything.
    >
    > * For 2.0 I would like the semantics to be homogenized, either by:
    >
    > * Changing yield (and assignment) to match the semantics
    > of the rest of the language,
    > or
    > * Making the behavior controllable by the user (on a case
    > by case basis) so that people who want the new behavior
    > can get it.
    >
    > [snip]


    T.
    trans. (T. Onoma), Sep 25, 2004
    #4
  5. Markus

    Markus Guest

    On Sat, 2004-09-25 at 09:52, ts wrote:
    > >>>>> "M" == Markus <> writes:

    >
    > M> * It does not appear to be possible to subclass Proc without
    > M> getting the special case behavior
    >
    > svg% cat b.rb
    > #!/usr/bin/ruby
    > class A < Proc
    > def self.new(&block)
    > proc(&block)
    > end
    > end
    >
    > a = A.new {|a, *b| p a, b }
    > a.call([1, 2, 3])
    > svg%


    But a.class is Proc, not A.

    -- Markus
    Markus, Sep 25, 2004
    #5
  6. Markus

    Markus Guest

    Re: Proc / def / yield semantics (short)

    On Sat, 2004-09-25 at 09:59, Brian Candler wrote:
    > I support all that you have written, as I have been bitten by this
    > inconsistency in the past (in a less serious way than you).
    > [snip]
    > So perhaps all we need to do, for ruby-2.0 at least, is keep this mechanism
    > and get rid of the rule which says "if single argument is an array then
    > auto-expand it" completely.


    That was my conclusion as well.

    > (3) It may be possible to keep Ruby's automagic exploding of arguments, if
    > we limit it to cases where there is no ambiguity. I am thinking of:
    >
    > - the sender passes a single array value;
    > - the receiver REQUIRES two or more arguments.
    >
    > This is unlike def foo(head,*rest) or proc {|head,*rest| ... }, where it can
    > take _one_ or more arguments, so the rule would not apply.
    >
    > This might be a solution for 1.8.2, where you want to be backwards
    > compatible. At least, Hash#each { |key,value| ... } would continue to work.
    > But it still wouldn't be consistent, unless you also applied it also to
    > method calls (which would be dubious, IMO)


    I played around with this idea (briefly) yesterday before posting.
    I reached the (tentative) conclusion that any attempt to retain
    auto-explosion and enforce consistency would blow up. The goatch for
    this one: if a method required two arguments and was passed an array by
    some user, changing the method so that the second argument was optional
    would (instead of required) would silently change what was passed in for
    the second argument--it would now get the array, and the second would
    get the default.

    You can move the problem around, but getting rid of implicit array
    explosion entirely seems to be the only cure.

    -- Markus
    Markus, Sep 25, 2004
    #6
  7. Actually, Markus, your examples don't work in 1.8.2 at all -- neither
    the tickl() method or the tickler proc. These do:

    def tickle(head, *rest)
    head.giggle if head.respond_to? :giggle
    tickle(*rest) unless rest.empty?
    end

    tickler = Proc.new { |head, *rest|
    head.giggle if head.respond_to? :giggle
    tickler.call(*rest) unless rest.empty?
    }

    class Giggler
    def initialize(pos)
    @pos = pos
    end

    def giggle
    puts "#{@pos}: giggle"
    end
    end

    a = []
    10.times do |pos|
    if (rand < 0.501)
    a << Giggler.new(pos)
    else
    a << Object.new
    end
    end
    p a

    tickle(*a)
    tickler[*a]

    I don't claim to understand the difference, really -- with the
    exception of the yield capability in Hash#each, I don't tend to rely
    on auto-expansion, instead being explicit.

    -austin
    Austin Ziegler, Sep 26, 2004
    #7
  8. Markus

    Markus Guest

    On Sat, 2004-09-25 at 19:52, Austin Ziegler wrote:
    > Actually, Markus, your examples don't work in 1.8.2 at all -- neither
    > the tickl() method or the tickler proc. These do:


    Yes, thanks. I should have included my usual caveat about code
    typed directly into a mail message.

    > with the exception of the yield capability in Hash#each, I don't
    > tend to rely on auto-expansion, instead being explicit.


    That's been my position as well. The problem here being that (as
    of 1.8.1) you get auto-expansion whether you want it or not. I don't
    object at all to having expansion available in situations where it is
    explicitly requested; in fact, I consider it a valuable feature, for
    which I am more than happy to pay an "*". But serious semantic tangles
    arise when array expansion is automatic and implicit under some
    circumstances, with no way to turn it off or even detect that it has
    been inflicted upon you.

    -- Markus

    P.S. I'm starting to question whether it is even needed for Hash#each; I
    can see at least two alternative ways to conceptualize the present
    behavior of Hash#each (one of which works nicely with the "association"
    extension proposed a week or so ago) without using implicit array
    expansion at all. If the idea holds up after some food & sleep &
    further mulling I'll post the details.
    Markus, Sep 26, 2004
    #8
  9. Re: Proc / def / yield semantics (short)

    On Sun, Sep 26, 2004 at 04:19:55AM +0900, Markus wrote:
    > > (3) It may be possible to keep Ruby's automagic exploding of arguments, if
    > > we limit it to cases where there is no ambiguity. I am thinking of:
    > >
    > > - the sender passes a single array value;
    > > - the receiver REQUIRES two or more arguments.
    > >
    > > This is unlike def foo(head,*rest) or proc {|head,*rest| ... }, where it can
    > > take _one_ or more arguments, so the rule would not apply.

    ...
    > I played around with this idea (briefly) yesterday before posting.
    > I reached the (tentative) conclusion that any attempt to retain
    > auto-explosion and enforce consistency would blow up. The goatch for
    > this one: if a method required two arguments and was passed an array by
    > some user, changing the method so that the second argument was optional
    > would (instead of required) would silently change what was passed in for
    > the second argument--it would now get the array, and the second would
    > get the default.


    True, but if you change the number of parameters which a (method|proc|block)
    expects, then you're almost certain to change the semantics anyway.

    In the case I'm thinking of, Ruby would otherwise have raised an
    "ArgumentError: wrong number of arguments (1 for 2)". So you're still
    changing the semantics by making the second argument optional.

    So I believe it's a question of which is more useful: the ArgumentError
    (perhaps making it quicker to track bugs due to wrong number of arguments
    being passed), or to auto-explode.

    Somehow I feel happier retaining this for blocks, for backwards-
    compatibility with 1.8.x, than to introduce it for method calls for
    consistency:

    def foo(a,b)
    # ...
    end

    a = [1,2]
    foo(a) # Feels like this *ought* still to raise an ArgumentError

    How do people feel about

    myhash.each{|key,value| ... }

    raising an ArgumentError, forcing you to rewrite it as

    myhash.each{|(key,value)| ... }

    ?

    Regards,

    Brian.
    Brian Candler, Sep 26, 2004
    #9
  10. Re: Proc / def / yield semantics (short)

    On Mon, 27 Sep 2004 00:32:01 +0900, Brian Candler <> wrote:
    > How do people feel about
    >
    > myhash.each{|key,value| ... }
    >
    > raising an ArgumentError, forcing you to rewrite it as
    >
    > myhash.each{|(key,value)| ... }
    >


    -1

    I can't stand the |(...)| notation.

    -austin
    --
    Austin Ziegler *
    * Alternate:
    : as of this email, I have [ 6 ] Gmail invitations
    Austin Ziegler, Sep 26, 2004
    #10
  11. Markus

    Markus Guest

    Re: Proc / def / yield semantics

    On Sun, 2004-09-26 at 08:32, Brian Candler wrote:
    > On Sun, Sep 26, 2004 at 04:19:55AM +0900, Markus wrote:
    > > > (3) It may be possible to keep Ruby's automagic exploding of arguments, if
    > > > we limit it to cases where there is no ambiguity. I am thinking of:
    > > >
    > > > - the sender passes a single array value;
    > > > - the receiver REQUIRES two or more arguments.
    > > >
    > > > This is unlike def foo(head,*rest) or proc {|head,*rest| ... }, where it can
    > > > take _one_ or more arguments, so the rule would not apply.

    > ...
    > > I played around with this idea (briefly) yesterday before posting.
    > > I reached the (tentative) conclusion that any attempt to retain
    > > auto-explosion and enforce consistency would blow up. The goatch for
    > > this one: if a method required two arguments and was passed an array by
    > > some user, changing the method so that the second argument was optional
    > > would (instead of required) would silently change what was passed in for
    > > the second argument--it would now get the array, and the second would
    > > get the default.

    >
    > True, but if you change the number of parameters which a (method|proc|block)
    > expects, then you're almost certain to change the semantics anyway.


    No. Consider: you have something (in working code) that accepts
    two parameters, and every time it is called, it receives two
    parameters. The code, as written, has been well tested and is known to
    work.
    If you add a default value to the second parameter, for use by
    newly written code, you would expect the old code (which always provides
    two values) to continue to function correctly. It would, under 1.8.0,
    and any other language I can think of that allows for default
    parameters.

    But if some users were depending on auto-array-explosion under the
    semantics proposed above, the old code would silently break; the array
    that had auto-expanded when there were two REQUIRED parameters would
    quietly stop exploding.

    > In the case I'm thinking of, Ruby would otherwise have raised an
    > "ArgumentError: wrong number of arguments (1 for 2)". So you're still
    > changing the semantics by making the second argument optional.


    No, under your proposed semantics it would have otherwise
    auto-exploded because:
    > > > - the sender passes a single array value;
    > > > - the receiver REQUIRES two or more arguments.



    > Somehow I feel happier retaining this for blocks, for backwards-
    > compatibility with 1.8.x, than to introduce it for method calls for
    > consistency:
    >
    > def foo(a,b)
    > # ...
    > end
    >
    > a = [1,2]
    > foo(a) # Feels like this *ought* still to raise an ArgumentError


    I agree.


    > How do people feel about
    >
    > myhash.each{|key,value| ... }
    >
    > raising an ArgumentError, forcing you to rewrite it as
    >
    > myhash.each{|(key,value)| ... }


    My present thinking is that this is a red herring. We've been
    talking as if Hash#each perforce uses default auto array explosion,
    assuming that it is internally analogous to:

    yield(kv_array)

    ... { |kv| ...
    ... { |k,v| ...

    and thus

    kv = kv_array
    k,v = kv_array

    where the latter case requires auto array explosion.

    But:

    1. As people have previously pointed out, there is no actual
    kv_array; it should, if anything, be a new class (an
    Association)
    2. Since the array supposedly being exploded is inside the
    definition of Hash#each, it would only have to be changed in
    this one place (by adding an "*") to make it explicit in any
    case.
    3. What's really going on is auto IMPLOSION, analogous to:

    yield(k0,v0)

    ... { |kv| ...
    ... { |k,v| ...

    and thus

    kv = k0,v0
    k,v = k0,v0

    ...all of which would work exactly as before, even if
    auto-explosion was completely eradicated from the language.
    (Auto implosion appears to be much less problematic since there
    is only one mechanism that I am aware of to pass around lvalue
    lists, and no l-side "default destination" analog to default
    rvalues).


    -- Markus
    Markus, Sep 26, 2004
    #11
  12. Markus

    Markus Guest

    Re: Proc / def / yield semantics (short)

    On Sun, 2004-09-26 at 09:59, Austin Ziegler wrote:
    > On Mon, 27 Sep 2004 00:32:01 +0900, Brian Candler <> wrote:
    > > How do people feel about
    > >
    > > myhash.each{|key,value| ... }
    > >
    > > raising an ArgumentError, forcing you to rewrite it as
    > >
    > > myhash.each{|(key,value)| ... }
    > >

    >
    > -1
    >
    > I can't stand the |(...)| notation.


    I'm not crazy about it myself, though I suspect my prefered
    alternative:

    myhash.each {|[key,value]| ... }

    may be as objectionable to you. In any case, it appears to be a red
    herring (see my response to Brian).

    -- Markus
    Markus, Sep 26, 2004
    #12
  13. Re: Proc / def / yield semantics (short)

    > No. Consider: you have something (in working code) that accepts
    > two parameters, and every time it is called, it receives two
    > parameters. The code, as written, has been well tested and is known to
    > work.
    > If you add a default value to the second parameter, for use by
    > newly written code, you would expect the old code (which always provides
    > two values) to continue to function correctly. It would, under 1.8.0,
    > and any other language I can think of that allows for default
    > parameters.
    >
    > But if some users were depending on auto-array-explosion under the
    > semantics proposed above, the old code would silently break; the array
    > that had auto-expanded when there were two REQUIRED parameters would
    > quietly stop exploding.


    OK, point taken; a quick check confirms you can make optional parameters in
    blocks, i.e.
    foo { |a,*b| p a,b }

    [although not |a,b=nil| like you can have in method definitions]

    > 1. As people have previously pointed out, there is no actual
    > kv_array; it should, if anything, be a new class (an
    > Association)
    > 2. Since the array supposedly being exploded is inside the
    > definition of Hash#each, it would only have to be changed in
    > this one place (by adding an "*") to make it explicit in any
    > case.


    It's a bit of a pain to fiddle with the internal working of existing
    iterators though; you'd have to wrap them something like

    class Foo
    def exploded_each(*args)
    each(*args) { |a| yield *a }
    end
    end

    > 3. What's really going on is auto IMPLOSION, analogous to:
    >
    > yield(k0,v0)
    >
    > ... { |kv| ...
    > ... { |k,v| ...


    Ah, right. I was assuming Hash#each doing "yield [k,v]", when it seems it
    actually does "yield k,v" (yep, looking at each_pair_i in hash.c)

    In that case, if it can be imploded into a pair [k,v] automatically for the
    purposes of Enumerable, then perhaps you're right, the problem goes away.

    In which case, why does auto-explosion happen at all? Can someone provide
    some compelling examples where it is needed?

    Regarding Associations though: if Hash#each did start returning these, you
    would clearly not be able to do

    myhash.each { |k,v| ... }

    any more. So you'd probably want a new interator for that case:

    myhash.each_pair { |k,v| ... } # already exists
    or
    myhash.each_with_index { |v,k| ... } # like an array

    Regards,

    Brian.
    Brian Candler, Sep 26, 2004
    #13
  14. Markus

    Markus Guest

    Re: Proc / def / yield semantics

    On Sun, 2004-09-26 at 11:05, Brian Candler wrote:

    > Regarding Associations though: if Hash#each did start returning these, you
    > would clearly not be able to do
    >
    > myhash.each { |k,v| ... }
    >
    > any more. So you'd probably want a new interator for that case:
    >
    > myhash.each_pair { |k,v| ... } # already exists
    > or
    > myhash.each_with_index { |v,k| ... } # like an array


    Perhaps. I keep feeling that there is an "Ah ha!" lurking in here
    somewhere--if we just look at things on the right way, we could (for
    2.0) get nearly full backward compatibility, cleaner semantics, and
    nice route for expanded expressiveness. I'll post more if the idea
    still seems reasonable after I think on it for a day or so...

    -- Markus
    Markus, Sep 26, 2004
    #14
  15. Markus

    Mark Hubbart Guest

    Re: Proc / def / yield semantics

    On Sep 26, 2004, at 11:54 AM, Markus wrote:

    > On Sun, 2004-09-26 at 11:05, Brian Candler wrote:
    >
    >> Regarding Associations though: if Hash#each did start returning
    >> these, you
    >> would clearly not be able to do
    >>
    >> myhash.each { |k,v| ... }
    >>
    >> any more. So you'd probably want a new interator for that case:
    >>
    >> myhash.each_pair { |k,v| ... } # already exists
    >> or
    >> myhash.each_with_index { |v,k| ... } # like an array

    >
    > Perhaps. I keep feeling that there is an "Ah ha!" lurking in here
    > somewhere--if we just look at things on the right way, we could (for
    > 2.0) get nearly full backward compatibility, cleaner semantics, and
    > nice route for expanded expressiveness. I'll post more if the idea
    > still seems reasonable after I think on it for a day or so...


    If Association is a subclass of Array or Values, then it should be
    possible to splat it. Here's an extremely bare-bones version
    demonstrating the possible behavior:

    class Association < Values
    def initialize(key, value)
    self.replace [key, value]
    end
    # a simple formatter for inspections, prints "key => value"
    def inspect
    map{|item| item.inspect}.join " => "
    end
    end
    ==>nil

    # Examples:

    h.each{|assoc| p assoc} # inspects the associations
    :b => 2
    :c => 3
    :a => 1
    ==>{:b=>2, :c=>3, :a=>1}
    h.each{|(key,val)| puts "key = #{key}, value = #{val}"} # list should
    expand
    key = b, value = 2
    key = c, value = 3
    key = a, value = 1
    ==>{:b=>2, :c=>3, :a=>1}

    Note that currently, you don't even have to write |(key,val)|, it will
    auto-expand. But I don't think that's future-proof.

    It's a neat idea, but I would wonder how practical it would be; I get
    the feeling our nice fast hash lookups would be ruined... But since I
    don't know the internals...

    cheers,
    Mark

    >
    > -- Markus
    >
    >
    Mark Hubbart, Sep 26, 2004
    #15
  16. Re: Proc / def / yield semantics (short)

    On Sep 26, 2004, at 10:59 AM, Austin Ziegler wrote:
    > On Mon, 27 Sep 2004 00:32:01 +0900, Brian Candler
    > <> wrote:
    >> How do people feel about
    >>
    >> myhash.each{|key,value| ... }
    >>
    >> raising an ArgumentError, forcing you to rewrite it as
    >>
    >> myhash.each{|(key,value)| ... }


    #each_pair exists...I never even knew you could throw two params in the
    block for #each until recently.

    Thus, I don't personally care if it raises an error if you don't wrap
    them in parens (although I don't like the |(aaa,bbb)| look) or fails to
    work altogether :)
    Gavin Kistner, Sep 30, 2004
    #16
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jiong Feng
    Replies:
    0
    Views:
    796
    Jiong Feng
    Nov 19, 2003
  2. David Lozzi
    Replies:
    3
    Views:
    1,917
    David Lozzi
    Jun 1, 2005
  3. NevilleDNZ
    Replies:
    9
    Views:
    423
    NevilleDNZ
    Aug 16, 2006
  4. Replies:
    1
    Views:
    314
    Gabriel Genellina
    Apr 22, 2008
  5. Markus
    Replies:
    1
    Views:
    187
    Mark Hubbart
    Sep 27, 2004
Loading...

Share This Page