On 2009-11-17, Marnen Laibow-Koser <marnen / marnen.org> wrote:
> I didn't say the same object -- I said the same *thing*.
> a = Stat.new(15, -2)
> b = Stat.new(15, -2)
> It doesn't matter in the slightest whether a.object_id == b.object_id. 
> Regardless, a and b are *the same thing* to all intents and purposes -- 
> and you can tell that, in part, precisely because the object_id is 
> irrelevant.

Perhaps, but (15, -2) and (14, -1) are also *equal*, even though they're
obviously different things.

Two fives equal a ten.  When I am talking about money, all I care about
is the net value, not the internal representation (coins or bills).  Unless
I have to deal with a vending machine.

But 99% of the time, 10==10 is the correct interpretation of a comparison
between two fives and a ten.

> call.  You're confusing the method with its return value.  The 
> difference is subtle but important.

Hmm.

Oh, heyyyyy.

Okay, so one option would be:
	def str
	  @str.to_i
	end

In short, just return the value rather than the object I'm using to model
it.

The problem is, I sort of want to be able to do things like:
	john.str.add_modifier("drunk", -3)

Because really, the fact that str is a stat *is* part of the intended
published interface.

It's just that, if you aren't doing something uniquely stat-like to a
stat, you just want its integer value.

> English: John's strength, in the abstract, isn't the same thing as 
> Mary's strength in the abstract.
> Ruby: john.method(:strength) != mary.method(:strength)
>
> English: The current value of John's strength happens to be equal to the 
> current value of Mary's strength.
> Ruby: john.strength == mary.strength
>
> Does this make sense?

Yup.

> Why?  There's no point to doing so as far as I can see.  Even if it were 
> a Fixnum, it would already be yielding different objects every time it 
> changed.  The client should make no assumptions about 
> john.strength.object_id.

Not as a specified interface that people should rely on, but rather, as an
implementation choice -- it seems crazy to me to regenerate whole object-trees
frequently.

>> This sounds extremely expensive for an extremely commonplace thing.  In
>> an ordinary turn of a game, I might have thirty or forty modifiers which
>> change, meaning that I'd be duplicating-but-modifying non-trivial object
>> trees thirty or forty times.

> It may sound expensive, but it really is the best way of handling value 
> objects.  This is how classes like Fixnum, Date, and BigDecimal work. 
> (Actually, Fixnum uses the Flyweight pattern under the hood; the others 
> might also.)

None of those classes are complicated trees containing five or ten or
thirty other objects, though, are they?

>> History of previous values (in some cases), and the modifiers aren't
>> fixnums; they're name/value pairs 

> Really?  You're not just doing
> @modifiers = {:racial => 2, :class => 1, :armor => -1}?

Modifier is a class too, which can handle things like counting down its
duration, etcetera.

And a stat has a handful of them.

>> probably plus other traits such as
>> a duration (some expire, some don't). 

> Does that really belong in the values itself?  I'd tend to think not; 
> rather, that probably belongs in your event system.

It seems to me that the knowledge that a modifier expires is best handled
by embedding it in the modifier.

> But if you want history, then the StatValue object or whatever you wind 
> up using will probably have to be immutable anyway, so you can be sure 
> your history is accurate!

Ahh, but I don't necessarily care whether it's accurate, just whether I
have a record.

For a concrete example:  A lot of games would have, say, a strength stat,
and things that can lower your strength.  Then you find a "potion of
restore strength", which restores your strength to its highest previous
value.

To my mind, this is the kind of thing that should be handled entirely
by the stat object.  It's not john's job to know what his highest score
was for a given stat; it's the stat's job to know what its highest score
was.

> Then refactor your design at that time.  Design for what you have now, 
> rather than predicting the future.  Remember YAGNI.

In this case, though, I really do know that I need a fair bit of this.
The ability to damage and restore stats is pretty much essential to a
roguelike.

> Since you don't know yet, don't design yet for them (your future plans 
> could easily change).  Design a model that works for what you actually 
> have, and be ready to change it as necessary.

Yeah, but changing this into an immutable object is a lot MORE work.  So
I'm not sure I should put extra work into making the object less flexible
on the off chance that I won't later not need to have done that work.

> Largely correct, although I can think of exceptions.  You'll probably 
> want to break this down into a composed object containing one or more 
> value objects.

I think so.

> No need.  The former is a lot friendlier.  You can always redefine 
> Character#strength= .

True.

> That strikes me as silly.  There's no reason that the client class 
> should care whether your strength= method *really* does an assignment 
> operation.

True.

Hmm.

So yeah, I think at this point, I really do want a complicated composite
object, which happens to delegate to a value object which has traits which
are useful for my purposes -- in this case, almost certainly a fixnum.

-s
-- 
Copyright 2009, all wrongs reversed.  Peter Seebach / usenet-nospam / seebs.net
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!