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

On Wed, 13 Dec 2006, Ryan W. wrote:

end
block.call

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

Hi –

On Wed, 13 Dec 2006, Ryan W. 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:

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

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

On 12/12/06, Ryan W. [email protected] wrote:

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:

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 W. [email protected] wrote:

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)

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.

Citát Ryan W. [email protected]:

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 V.

Citát Ryan W. [email protected]:

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 V.

Ryan W. wrote:

attributes that could be assigned either a literal value, or a block that

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.

One more thing.

Citát Ryan W. [email protected]:

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 V.