Assignment method strangeness

Assignment methods are weird, and I wish they acted more like regular
methods. It seems like they could be, but the parser doesn't properly parse
the usage. Let me show you what I mean.

Assignment methods can't take more than one argument:

class Test
  def d=(a,b)
    puts "A: #{a}, B: #{b}"
  end
end

test.d = 1, 2 #=>ArgumentError: wrong number of arguments (1 for 2)
test.d = [1, 2] #=> ArgumentError: wrong number of arguments (1 for 2)
test.d = *[1, 2] #=> ArgumentError: wrong number of arguments (1 for 2)

Assignment methods can't take blocks:

class Test
  def t=(t, &block)
    @t = t
    block.call
  end
end

test = Test.new
test.t = 5 { puts "block" } #=> SyntaxError: compile error
                                      #=> parse error, unexpected '{',
expecting $

You can't seem to replace the single required argument with a block:

class Test
  def v=(&block)
    block.call
  end
end

test = Test.new
test.v ={ puts "block" } #=> Exception: wrong number of arguments (1 for
0)

You can, of course, get around these restrictions with #send

test.send(:d=, 1, 2) #=> A: 1, B: 2

Though you can define these methods all you want, it seems as though the
parser that does the syntactic sugaring at the call location makes some very
narrow assumptions about the nature of the assignment method. Is there any
way around this?

-RYaN

harp:~ > cat a.rb
   require 'rubygems'
   require 'attributes'

   class Test
     attribute 't'
   end

   test = Test.new

   test.t 42
   p test.t

   harp:~ > ruby a.rb
   42

it looks really nice with classes:

     harp:~ > cat a.rb
     require 'rubygems'
     require 'attributes'

     class Base
       class << self
         attribute 'a' => 4
         attribute 'b' => 2
         attribute 'c' # no default value
       end
     end

     class Derived < Base
       c 'forty-two'
     end

     p [Derived.a, Derived.b]
     p Derived.c

     harp:~ > ruby a.rb
     [4, 2]
     "forty-two"

if you don't want to install the gem just download and cut and paste the code:
it's on 42 lines long!

-a

···

On Wed, 13 Dec 2006, Ryan Williams wrote:

Assignment methods are weird, and I wish they acted more like regular
methods. It seems like they could be, but the parser doesn't properly parse
the usage. Let me show you what I mean.

Assignment methods can't take more than one argument:

class Test
def d=(a,b)
  puts "A: #{a}, B: #{b}"
end

test.d = 1, 2 #=>ArgumentError: wrong number of arguments (1 for 2)
test.d = [1, 2] #=> ArgumentError: wrong number of arguments (1 for 2)
test.d = *[1, 2] #=> ArgumentError: wrong number of arguments (1 for 2)

Assignment methods can't take blocks:

class Test
def t=(t, &block)
  @t = t
  block.call
end

test = Test.new
test.t = 5 { puts "block" } #=> SyntaxError: compile error
                                    #=> parse error, unexpected '{',
expecting $

You can't seem to replace the single required argument with a block:

class Test
def v=(&block)
  block.call
end

test = Test.new
test.v ={ puts "block" } #=> Exception: wrong number of arguments (1 for
0)

You can, of course, get around these restrictions with #send

test.send(:d=, 1, 2) #=> A: 1, B: 2

Though you can define these methods all you want, it seems as though the
parser that does the syntactic sugaring at the call location makes some very
narrow assumptions about the nature of the assignment method. Is there any
way around this?

-RYaN

--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

Hi --

Assignment methods are weird, and I wish they acted more like regular
methods. It seems like they could be, but the parser doesn't properly parse
the usage. Let me show you what I mean.

Assignment methods can't take more than one argument:

...

Assignment methods can't take blocks:

...

You can't seem to replace the single required argument with a block:

...

Though you can define these methods all you want, it seems as though the
parser that does the syntactic sugaring at the call location makes some very
narrow assumptions about the nature of the assignment method. Is there any
way around this?

I think the idea of an assignment method *is* rather narrow. If you
have things like this:

   person.name = "David" { ...}

then the assignment-method syntacs and semantics have branched so far
away from those of assignments that you might as well do:

   person.set_name("David") { ... }

which would certainly be one way around it, though I like the look of
the equal-sign methods most of the time.

David

···

On Wed, 13 Dec 2006, Ryan Williams wrote:

--
Q. What's a good holiday present for the serious Rails developer?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black\)
    aka The Ruby book for Rails developers!
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

Here's what the parse tree looks like for:
test.d = 1
[:attrasgn, [:vcall, :test], :d=, [:array, [:lit, 1]]]

..and for:
test.d = 1,2
[:attrasgn, [:vcall, :test], :d=,
  [:array, [:svalue, [:array, [:lit, 1], [:lit, 2]]]]]

As you can see in the first one.. even a single value passed to an
assignment is parsed as an array. If you need multiple arguments to an
assignment, you have to handle them inside the method body itself.

···

On 12/12/06, Ryan Williams <mr.cruft@gmail.com> wrote:

Assignment methods are weird, and I wish they acted more like regular
methods. It seems like they could be, but the parser doesn't properly parse
the usage. Let me show you what I mean.

Assignment methods can't take more than one argument:

class Test
  def d=(a,b)
    puts "A: #{a}, B: #{b}"
  end
end

test.d = 1, 2 #=>ArgumentError: wrong number of arguments (1 for 2)
test.d = [1, 2] #=> ArgumentError: wrong number of arguments (1 for 2)
test.d = *[1, 2] #=> ArgumentError: wrong number of arguments (1 for 2)

Citát Ryan Williams <mr.cruft@gmail.com>:

Assignment methods are weird, and I wish they acted more like regular
methods. It seems like they could be, but the parser doesn't properly parse
the usage. Let me show you what I mean.

This is the third thread on the subject in the past month, IIRC. On the first
one, The Matz Hath Spoken:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/227883

This was an intentional change between 1.6 and 1.8, I think. Assignment is
assignment is assignment, if you do wildly un-assignmentlike things with it,
you'll confuse people reading your code - and you can as well use a method
without syntactic sugar around it for that.

Assignment methods can't take more than one argument:

My best bet is that this is to avoid possible ambiguous parsing for parallel
assignment, which happily works for object attributes too:

irb(main):001:0> class Foo
irb(main):002:1> attr_accessor :bar, :baz
irb(main):003:1> end
=> nil
irb(main):004:0> foo = Foo.new
=> #<Foo:0x2f1924c>
irb(main):005:0> foo.bar, foo.baz = 'BAR', 'BAZ'
=> ["BAR", "BAZ"]
irb(main):006:0> foo.bar
=> "BAR"
irb(main):007:0> foo.baz
=> "BAZ"

class Test
  def v=(&block)
    block.call
  end
end

test = Test.new
test.v ={ puts "block" } #=> Exception: wrong number of arguments (1 for
0)

If I ever saw code where you go

Is there any way around this?

At the risk of being painfully obvious, don't use an assignment? The assignment
method syntactic sugar is there to make setters, that is, methods that *set a
value to an attribute* look prettier and more importantly, behave consistently
within the language and with other common programming languages.

David Vallner

As you can see in the first one.. even a single value passed to an
assignment is parsed as an array. If you need multiple arguments to an
assignment, you have to handle them inside the method body itself.

You are correct. That's pretty useful, though you still can't pass in
blocks.

The reason I was interested in this was that I wanted to create some
attributes that could be assigned either a literal value, or a block that
returned the literal value when called. Then you could do some really lazy
evaluation, as in:

test.a = { long_computation }
....
...in another scope...
....
test.a # returns result of computation

Or you could use it to keep some objects in sync:

test.a = { some_other_object.b }
some_other_object.b = 42
test.a = 42

I guess the best route to this is either a new assignment-like method (
test.set_a) or test.a = Proc.new.

-RYaN

test.a = 42

Whoops, I meant:
  test.a # => 42

Check this out; it may be helpful to you. Pretty similar. It differs
only in where the block is written, but that would be easy to change.

http://www.rubyquiz.com/quiz67.html

···

On 12/12/06, Ryan Williams <mr.cruft@gmail.com> wrote:

>
> As you can see in the first one.. even a single value passed to an
> assignment is parsed as an array. If you need multiple arguments to an
> assignment, you have to handle them inside the method body itself.
>
You are correct. That's pretty useful, though you still can't pass in
blocks.

The reason I was interested in this was that I wanted to create some
attributes that could be assigned either a literal value, or a block that
returned the literal value when called. Then you could do some really lazy
evaluation, as in:

Citát Ryan Williams <mr.cruft@gmail.com>:

test.a = { some_other_object.b }
some_other_object.b = 42
test.a = 42

1. The sheer memory leak of it all. Ruby apparently isn't made to be a good
functional language, even if it's very doable. Also, it looks like a design
smell to me - why can't you use some_other_object.b in the first place, or move
the data into test.a or somewhere else altogether? To me it seems you'd use that
when you can't decide where some data / state actually belongs.

2. Any code where:

foo.bar = baz
foo.bar == baz # => false

makes me want to scream. YMMV. Assignment being a syntactic builtin to the
language with a certain behaviour is a notion too burned into my brain for
juggling the possibility that it isn't to be an option.

David Vallner

One more thing.

Citát Ryan Williams <mr.cruft@gmail.com>:

test.a = { some_other_object.b }
some_other_object.b = 42
test.a = 42

I guess the best route to this is either a new assignment-like method (
test.set_a) or test.a = Proc.new.

I'd be in favour of a method called alias_a. Or make a generic method called as
alias_attribute(:a) {some_other_object.b}

The behaviour you describe to me seems MUCH more related to the builtin method
aliasing than assignment.

David Vallner

Ryan Williams wrote:

>
> As you can see in the first one.. even a single value passed to an
> assignment is parsed as an array. If you need multiple arguments to an
> assignment, you have to handle them inside the method body itself.
>
>
You are correct. That's pretty useful, though you still can't pass in
blocks.

The reason I was interested in this was that I wanted to create some
attributes that could be assigned either a literal value, or a block that
returned the literal value when called. Then you could do some really lazy
evaluation, as in:

test.a = { long_computation }
....
...in another scope...
....
test.a # returns result of computation

Or you could use it to keep some objects in sync:

test.a = { some_other_object.b }
some_other_object.b = 42
test.a = 42

I guess the best route to this is either a new assignment-like method (
test.set_a) or test.a = Proc.new.

  class Test
    def a=(x)
      case x
      when Proc
         puts "proc!"
      else
         puts "value"
      end
    end
  end

  test = Test.new
  test.a = lamba { some_other_object.b }

also if you do need more than one arg:

  def a(x=Exception, y=nil)
    return @a if x == Exception
    @a = x + y # or whatever
  end

t.