Newbie question: Defining a numeric type

S

Seebs

I have a type which has a bit of internal magic, but fundamentally, I want
it to behave for most purposes like the value it yields from to_i/to_int.

Basically, is there a way to avoid having to write +, -, etcetera, when
in each case it'd be:
def <op>(other)
self.to_i <op> other
end

I think I'm thinking something like the Comparable mixin; sort of a
"I have a to_i, make me a number" module.

-s
 
M

Mario Camou

Have a look at Forwardable

I have a type which has a bit of internal magic, but fundamentally, I wan= t
it to behave for most purposes like the value it yields from to_i/to_int.

Basically, is there a way to avoid having to write +, -, etcetera, when
in each case it'd be:
=A0 =A0 =A0 =A0def <op>(other)
=A0 =A0 =A0 =A0 =A0self.to_i <op> other
=A0 =A0 =A0 =A0end

I think I'm thinking something like the Comparable mixin; sort of a
"I have a to_i, make me a number" module.

-s

--=20
-Mario.
 
M

Marnen Laibow-Koser

Seebs said:
I have a type which has a bit of internal magic, but fundamentally, I
want
it to behave for most purposes like the value it yields from
to_i/to_int.

Does it need to be a class of its own? You could use a module and just
include it where necessary:

module Magical
def magic
...
end
end
...
...
@val = 5
class << @val
include Magical
end
@val.magic
Basically, is there a way to avoid having to write +, -, etcetera, when
in each case it'd be:
def <op>(other)
self.to_i <op> other
end

If you don't like the first solution, try:

[:+, :*, :-, :/, :%].each do |op|
define_method op {|other| self.send op, other}
end

You could wrap this in a module too.
I think I'm thinking something like the Comparable mixin; sort of a
"I have a to_i, make me a number" module.

-s

Best,
 
M

Marnen Laibow-Koser

Seebs said:
I have a type which has a bit of internal magic, but fundamentally, I
want
it to behave for most purposes like the value it yields from
to_i/to_int.

Does it need to be a class of its own? You could use a module and just
include it where necessary:

module Magical
def magic
...
end
end
...
...
@val = 5
class << @val
include Magical
end
@val.magic
Basically, is there a way to avoid having to write +, -, etcetera, when
in each case it'd be:
def <op>(other)
self.to_i <op> other
end

If you don't like the first solution, try:

[:+, :*, :-, :/, :%].each do |op|
define_method op {|other| self.to_i.send op, other}
end

You could wrap this in a module too.
I think I'm thinking something like the Comparable mixin; sort of a
"I have a to_i, make me a number" module.

-s

Best,
 
S

Seebs

Does it need to be a class of its own?

I think it makes more sense that way. It's a category of objects which
have common additional methods they support, but which can be used in nearly
any context where you could use an integer.
If you don't like the first solution, try:

[:+, :*, :-, :/, :%].each do |op|
define_method op {|other| self.to_i.send op, other}
end

You could wrap this in a module too.

Could, but...

The suggestion to try Forwardable got me to find SimpleDelegator, which
turns out to work beautifully.

Basically, I'm doing a roguelike mostly for fun and/or as a learning
exercise, and I wanted a way to encode "stats" -- things like strength
or intelligence, which characters have. Stats might have temporary
modifiers, or remember their highest previous value, or whatever... But
90% of the time, you just want to refer to them and get the value
they currently have. Making the user write "player.str.current_value"
is annoying; I'd rather just use "player.str". So having that delegate
to an internal member which really is just an integer works; then, whenever
that value changes, I point the delegator at it, and Everything Just
Works.

-s
 
M

Marnen Laibow-Koser

Seebs said:
I think it makes more sense that way. It's a category of objects which
have common additional methods they support, but which can be used in
nearly
any context where you could use an integer.

Then they should most likely be Integers. Extend them either with
module inclusion or by subclassing.
If you don't like the first solution, try:

[:+, :*, :-, :/, :%].each do |op|
define_method op {|other| self.to_i.send op, other}
end

You could wrap this in a module too.

Could, but...

The suggestion to try Forwardable got me to find SimpleDelegator, which
turns out to work beautifully.

Yeah, but it's probably not conceptually right for this case. Have you
been following the thread about using mixins rather than declaring new
classes?
Basically, I'm doing a roguelike mostly for fun and/or as a learning
exercise, and I wanted a way to encode "stats" -- things like strength
or intelligence, which characters have. Stats might have temporary
modifiers, or remember their highest previous value, or whatever... But
90% of the time, you just want to refer to them and get the value
they currently have. Making the user write "player.str.current_value"
is annoying; I'd rather just use "player.str". So having that delegate
to an internal member which really is just an integer works; then,
whenever
that value changes, I point the delegator at it, and Everything Just
Works.

If you use actual Integers as I suggested above, everything will Just
Work with less effort, and your design will be clearer.

Best,
 
B

Brian Candler

Seebs said:
I think I'm thinking something like the Comparable mixin; sort of a
"I have a to_i, make me a number" module.

You could just delegate to the number:

class Foo
def initialize(n)
@n = n
end
def to_int
@n
end
def method_missing(*args)
to_int.send(*args)
end
end

f = Foo.new(12)
puts f + 3
puts 2 + f

Note that there is a subtle distinction between to_int and to_i, as
there is between to_str and to_s. I think you want to_int here, since
you are declaring that your object is, to all intents and purposes, an
integer.

You could also look at the #coerce method, but I don't think it's needed
here.
 
M

Marnen Laibow-Koser

Seebs said:
Hmm. I tried subclassing, but perhaps incorrectly. The problem is that
it really is, internally, an object with several integers, but unless
you
know that and care about it, all you need is one particular one of those
integers.

Oh, then that's a different story. In this case, delegation is probably
the right thing to do.
Only sort of.


I'm curious about this, but I'm having a hard time wrapping my head
around it. So far as I can tell, unless the integer is a Bignum, it'll
be a Fixnum -- which means that any two things with the same value
are the same object, and so on.

But I don't want any two statistics which currently have the same
apparent
value to be the same object -- because I need to be able to, say, tack
on
a modifier to one of them and have it not affect the other.

Sure. I didn't realize you were doing things like that. And you're
right that the singleton nature of Bignums makes them less flexible in
this regard.
As an example, some hypothetical object might do something like:

x = Stat.new(10)
y = Stat.new(10)

x.adjust_to(15)

At this point, "x + 1" should be 16, and "y + 1" should be 11. If I
tacked
on a "+3" modifier to x, x would report itself as 18... But internally,
it's a 15 and a +3. 18 is just its value for most purposes.

I can't figure out how to express that by subclassing Integer.

It's probably not worth it. I didn't understand your structure
originally.
The stat
has several integers, although one of them is the one you probably want
if
you're trying to perform arithmetic on it.

Brian's suggestion of #coerce may be a good one.
(Note the "adjust_to" -- you can't, for obvious reasons, assign a new
value with =. Things which have stats define the attr= for those stats
to use adjust_to.)

Philosophical point: many people (myself included, I think) believe that
unlike entity objects (say, Player in your game), value objects should
be immutable. This means

class Stat
def adjust_to(n)
# bad:
@base = n
# good:
Stat.new(n, @modifier)
end
end

See http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable . Among other
things, this means you never have to worry about modifying an object
referred to in multiple places.

Best,
 
S

Seebs

Philosophical point: many people (myself included, I think) believe that
unlike entity objects (say, Player in your game), value objects should
be immutable. This means

class Stat
def adjust_to(n)
# bad:
@base = n
# good:
Stat.new(n, @modifier)
end
end

See http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable . Among other
things, this means you never have to worry about modifying an object
referred to in multiple places.

In this case, I think the correct model is that this isn't really a value
object, it's just an object which has a value. Imagine that I wanted to
talk about "my paycheck". Well, there's a base salary, and there's various
taxes and deductions and withholding... But except when I'm doing the tax
accounting, all I *really* care about is the take-home pay, so if I
refer to "salary" I probably mean the output value of all those calculations.

Basically, if someone else has a reference to a specific stat, and it
changes, I think they DO want to see the now-changed value.

-s
 
S

Seebs

Philosophical point: many people (myself included, I think) believe that
unlike entity objects (say, Player in your game), value objects should
be immutable. This means

class Stat
def adjust_to(n)
# bad:
@base = n
# good:
Stat.new(n, @modifier)
end
end

See http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable . Among other
things, this means you never have to worry about modifying an object
referred to in multiple places.

In this case, I think the correct model is that this isn't really a value
object, it's just an object which has a value. Imagine that I wanted to
talk about "my paycheck". Well, there's a base salary, and there's various
taxes and deductions and withholding... But except when I'm doing the tax
accounting, all I *really* care about is the take-home pay, so if I
refer to "salary" I probably mean the output value of all those calculations.

Basically, if someone else has a reference to a specific stat, and it
changes, I think they DO want to see the now-changed value.

-s
 
M

Marnen Laibow-Koser

Seebs said:
In this case, I think the correct model is that this isn't really a
value
object, it's just an object which has a value.

I think you are likely wrong. But there's a simple conceptual test you
can apply:

a = Stat.new(5)
b = Stat.new(5)

Should a == b return true or false?

If false, then this is probably not a value object in the conventional
sense.

If true, then it probably is.
Imagine that I wanted to
talk about "my paycheck". Well, there's a base salary, and there's
various
taxes and deductions and withholding... But except when I'm doing the
tax
accounting, all I *really* care about is the take-home pay, so if I
refer to "salary" I probably mean the output value of all those
calculations.

Poor analogy. I think your paycheck is like your character's entire
stat block, whereas the individual Stat objects seem to be analogous to
the dollar amounts on each like of the paycheck -- and those are
certainly value objects.
Basically, if someone else has a reference to a specific stat, and it
changes, I think they DO want to see the now-changed value.

And I think you're probably wrong.

john = Character.new
john.strength = Stat.new(15)
mary = Character.new
mary.strength = john.strength
mary.strength += 1

Now, what should john.strength be? 15 or 16?

Best,
 
S

Seebs

a = Stat.new(5)
b = Stat.new(5)

Should a == b return true or false?
If false, then this is probably not a value object in the conventional
sense.
If true, then it probably is.

Hmm.

I would think it should return true.
Poor analogy. I think your paycheck is like your character's entire
stat block, whereas the individual Stat objects seem to be analogous to
the dollar amounts on each like of the paycheck -- and those are
certainly value objects.

An individual stat might have:
base value 10
modifier (racial, no duration) +3
modifier (training, no duration) +2
modifier (drunk, duration 15 turns) -1

That's "a 14". It is *equal to* another stat which has a base value of 14
and no modifiers. But it's not the same *thing* as that other stat.
And I think you're probably wrong.

john = Character.new
john.strength = Stat.new(15)
mary = Character.new
mary.strength = john.strength
mary.strength += 1

Now, what should john.strength be? 15 or 16?

15. But you don't copy stats -- you use their values. So what really
happens is that mary defines strength= such that it applies modifiers
to mary.strength.

In fact, in "john.strength = Stat.new(15)", what really happens is that
john does the same thing -- adjust the already-existing stat so that it will
have an effective value of 15.

Hmm.

So, thinking about it, it seems like what I'm getting to is that really,
the "Stat" class is largely internal to characters. It's a way to express
something that, in general, no one else should use. (Not quite true;
you can query the modifiers that currently apply to a stat, for instance.)
For the purposes of nearly any possible interaction, john.strength is
just the value 15.

You're asking some really good questions here, thanks.

-s
 
M

Marnen Laibow-Koser

Seebs said:
Hmm.

I would think it should return true.

So would I.
An individual stat might have:
base value 10
modifier (racial, no duration) +3
modifier (training, no duration) +2
modifier (drunk, duration 15 turns) -1

That's "a 14". It is *equal to* another stat which has a base value of
14
and no modifiers. But it's not the same *thing* as that other stat.

Of course it isn't. Here's how I see this working (for simplicity, I'll
just assume one modifier):

a = Stat.new 10, 0
b = Stat.new 10, 2
c = Stat.new 12, -2
d = Stat.new 12, -2
c == d # true
a == b # false
a == c # false
a.to_i == c.to_i # true

Does that all seem right?
15. But you don't copy stats -- you use their values.

Right! In other words, they're identified by their value, not by the
individual object reference. Ergo, *they are value objects*, by
definition.
So what really
happens is that mary defines strength= such that it applies modifiers
to mary.strength.

Or simply creates a new immutable value object, which will save some
headache.
In fact, in "john.strength = Stat.new(15)", what really happens is that
john does the same thing -- adjust the already-existing stat so that it
will
have an effective value of 15.

No. If you call a constructor, you might as well use the object. :)
Hmm.

So, thinking about it, it seems like what I'm getting to is that really,
the "Stat" class is largely internal to characters. It's a way to
express
something that, in general, no one else should use. (Not quite true;
you can query the modifiers that currently apply to a stat, for
instance.)

So you *do* want to do something like john.strength.modifiers[:racial],
right? If so, then the Stat class is not internal. Personally, I think
that's fine -- a stat is not jet a simple number, and so you might as
well represent it with an appropriate value object.
For the purposes of nearly any possible interaction, john.strength is
just the value 15.

The easiest way to achieve that is with conversion and/or coercion.
You're asking some really good questions here, thanks.

-s

You're welcome! These are questions I struggle with as well -- I like
to learn by teaching.
Best,
 
S

Seebs

Of course it isn't. Here's how I see this working (for simplicity, I'll
just assume one modifier):

a = Stat.new 10, 0
b = Stat.new 10, 2
c = Stat.new 12, -2
d = Stat.new 12, -2
c == d # true
a == b # false
a == c # false
a.to_i == c.to_i # true
Does that all seem right?

Hmm.

I would actually expect a==c to be true. There's no point where I really
want to perform an object-identity test on them, so even then, I'm just
comparing their to_i values, which will ==, since they're both 10.
Right! In other words, they're identified by their value, not by the
individual object reference. Ergo, *they are value objects*, by
definition.

"identified" may not be the right term.

Two stats which yield the same result value may *compare* equal -- because
if you're comparing them you're just working with values. But that doesn't
mean that they're the same thing. The primary way you'd *identify* a stat
is by knowing which stat it is, for which entity.

So if something, for some reason, has intentionally obtained a reference
to john.strength, then it really does want to see the current value of
john.strength -- but that's the exceptional case, not the normal case, so
assigning it to mary.strength wouldn't do that.
No. If you call a constructor, you might as well use the object. :)

Except that people might already have, for some crazy reason, an intentional
reference into "the strength stat for john".

Although it occurs to me that there's really no REASON for them to ever do
that.
So you *do* want to do something like john.strength.modifiers[:racial],
right?
Maybe.

If so, then the Stat class is not internal. Personally, I think
that's fine -- a stat is not jet a simple number, and so you might as
well represent it with an appropriate value object.

So when the modifiers change, your solution would be to replace the entire
object, duplicating its whole set of modifiers and other state?

Hmm.

I guess my current thought is... A statistic really IS an object of its
own with complicated internal state which changes over time. It doesn't
make sense to replace and duplicate that potentially-quite-complicated
state. However, 90% of references will end up not caring about any of that
state, and only wanting the object's final-value -- which really IS a
value object.

So it would make some sense to just always right john.strength.value, except
that this would get to be really annoying, and since 90% of usages are going
to be like that, I come up with the alternative: Any reference to
john.strength in a context that looks like it might be arithmetic yields the
value, which is at any given time an immutable object, and bypasses the
large, complicated, mutable object.

-s
 
M

Marnen Laibow-Koser

Seebs said:
Hmm.

I would actually expect a==c to be true. There's no point where I
really
want to perform an object-identity test on them, so even then, I'm just
comparing their to_i values, which will ==, since they're both 10.

But you yourself said:

[...]
That's "a 14". It is *equal to* another stat which has a base value of 14
and no modifiers. But it's not the same *thing* as that other stat.

== means "conceptually the same thing", I think. Not necessarily the
same object reference -- but equivalent. So I actually don't think you
want a==c to be true. You may want a==10 to be true, but that's a
different story.
"identified" may not be the right term.

OK, think of it like this: if their values are the same, they are
considered equal. Stat.new(5) == Stat.new(5) even though Stat.new(5)
doesn't equal? Stat.new(5) -- that is, they are equal even though
they're not necessarily the same object.

How you define "values are the same" is up to you. I think it's best to
consider it as meaning total equality for both base and modifiers.
Two stats which yield the same result value may *compare* equal --
because
if you're comparing them you're just working with values. But that
doesn't
mean that they're the same thing.

Right. But two stats which have the same base and modifiers *are* the
same thing, aren't they? There's no reason to know whether they're
different objects in the VM -- for all practical purposes, they are *the
same thing*. And that fact (if it is a fact) means that we're talking
about value objects.

[...]
So if something, for some reason, has intentionally obtained a reference
to john.strength, then it really does want to see the current value of
john.strength

That's not the way Ruby references work. You can obtain a reference to
the object returned by john.strength at a given time, but you can't know
whether john.strength will always return that same object.

In other words, you could call john.strength.object_id and get a value
(let's say 12345). If you later call john.strength.object_id, the
returned value might or might not be 12345. The caller should make no
assumptions in this regard.

-- but that's the exceptional case, not the normal case,
so
assigning it to mary.strength wouldn't do that.



Except that people might already have, for some crazy reason, an
intentional
reference into "the strength stat for john".

There is no such thing in Ruby, as I explained above. There is nothing
really to be gained by keeping the object_id the same.
Although it occurs to me that there's really no REASON for them to ever
do
that.

And really no way to do it. (Even if they do figure out a way, that's
abuse of your interface. They shouldn't expect it to work.)

[...]
So you *do* want to do something like john.strength.modifiers[:racial],
right?
Maybe.

If so, then the Stat class is not internal. Personally, I think
that's fine -- a stat is not jet a simple number, and so you might as
well represent it with an appropriate value object.

So when the modifiers change, your solution would be to replace the
entire
object, duplicating its whole set of modifiers and other state?

Yes. That is the best way of handling value objects -- in fact, it's
the way Ruby handles Fixnums!

irb(main):001:0> a = 1
=> 1
irb(main):002:0> a.object_id
=> 3
irb(main):003:0> a += 5
=> 6
irb(main):004:0> a.object_id
=> 13

Object 1 is the Fixnum 1, and object 13 is the Fixnum 6. When the value
of a changes, the variable a holds an entirely different object
reference.
Hmm.

I guess my current thought is... A statistic really IS an object of its
own with complicated internal state which changes over time. It doesn't
make sense to replace and duplicate that potentially-quite-complicated
state.

Then maybe a statistic should be composed of several value objects. I
was operating under the assumption that it's just a base Fixnum and a
Hash containing a few modifier Fixnums. What else is there?
However, 90% of references will end up not caring about any of
that
state, and only wanting the object's final-value -- which really IS a
value object.

Right -- probably a Fixnum.
So it would make some sense to just always right john.strength.value,
except
that this would get to be really annoying, and since 90% of usages are
going
to be like that, I come up with the alternative: Any reference to
john.strength in a context that looks like it might be arithmetic yields
the
value, which is at any given time an immutable object,

Which you can do with delegation and coercion.
and bypasses the
large, complicated, mutable object.

From what you've described, Stat isn't large and complicated. What's
missing from your description?

Best,
 
S

Seebs

== means "conceptually the same thing", I think. Not necessarily the
same object reference -- but equivalent.

Hmm.

I guess I think of == as more like "functionally the same". Two 14s function
the same way. If the question is "who is stronger", then if both characters
have a current functional strength of 14, it's a tie.

But that doesn't make them the same object.

Basically: 14.0 == 14. That doesn't mean they're the same thing, because
obviously they're different things. And they're not even quite equivalent;
15 / 14.0 is not the same as 15 / 14. But they're still equal, because they
have the same value.
How you define "values are the same" is up to you. I think it's best to
consider it as meaning total equality for both base and modifiers.

I don't, though, because the only reason to be comparing two values is to
see whether one is larger than the other -- say, whether one creature is
stronger than another.
Right. But two stats which have the same base and modifiers *are* the
same thing, aren't they?

I don't think so. Even if john and mary both have the same base strength,
and the same modifiers, they're not the same thing -- making john stronger
doesn't make mary stronger.
That's not the way Ruby references work. You can obtain a reference to
the object returned by john.strength at a given time, but you can't know
whether john.strength will always return that same object.

Right -- which is why my initial plan was to commit that, after initialize(),
john.strength DOES always yield the same object, no matter what changes
are made to its value.
Yes. That is the best way of handling value objects -- in fact, it's
the way Ruby handles Fixnums!

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.
Object 1 is the Fixnum 1, and object 13 is the Fixnum 6. When the value
of a changes, the variable a holds an entirely different object
reference.

Yes, I know that.

But I don't think a stat is a value. I think it's a complicated model, which
happens to yield a value.
Then maybe a statistic should be composed of several value objects. I
was operating under the assumption that it's just a base Fixnum and a
Hash containing a few modifier Fixnums. What else is there?

History of previous values (in some cases), and the modifiers aren't
fixnums; they're name/value pairs probably plus other traits such as
a duration (some expire, some don't). There will probably be more
tweaks to that over time.
From what you've described, Stat isn't large and complicated. What's
missing from your description?

As time goes on, stats are expected to be much more complicated. A stat
will likely have its original starting base value, its highest recorded base
value, and a set of modifiers (which may change quite often). Possibly more
things beyond those.

So right now, I think it is probably nearly-sanest for me to have it delegate
nearly everything to the fixnum current value, which really is a value object.
So it isn't a value, but it delegates to one.

Basically, anything that has internal state to keep track of which is not part
of how you use it strikes me as not really being a value, even if it looks
like one from the outside.

That said!

I am thinking that perhaps it should *present* as immutable -- that is, I
should not define john.strength = 15 in any way, but rather, if you want to
ask it to change its value, you have to send it messages. So instead of
john.strength += 1, you'd write john.strength.adjust_by(1).

Hmm. I suppose the thing, then, would be to simply not provide a strength=
method for the things that have stats.

-s
 
M

Marnen Laibow-Koser

Seebs wrote:
[...]
I don't think so. Even if john and mary both have the same base
strength,
and the same modifiers, they're not the same thing -- making john
stronger
doesn't make mary stronger.

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. John's strength isn't the same thing as Mary's strength,
but that's a very different issue. The English concept "John's strength
(in the abstract)" is equivalent to the Ruby method call john.strength.
The English concept "the concrete value that, for the moment, represents
John's strength" is equivalent to the return value from that method
call. You're confusing the method with its return value. The
difference is subtle but important.

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?
Right -- which is why my initial plan was to commit that, after
initialize(),
john.strength DOES always yield the same object, no matter what changes
are made to its value.

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.
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.)
Yes, I know that.

But I don't think a stat is a value. I think it's a complicated model,
which
happens to yield a value.


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}?
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.
There will probably be more
tweaks to that over time.

OK, then this is getting more complex than a simple value object. If
you want history, maybe you should have a separate StatValue object, or
simply a hash.

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!
As time goes on, stats are expected to be much more complicated.

Then refactor your design at that time. Design for what you have now,
rather than predicting the future. Remember YAGNI.
A stat
will likely have its original starting base value, its highest recorded
base
value, and a set of modifiers (which may change quite often). Possibly
more
things beyond those.

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.
So right now, I think it is probably nearly-sanest for me to have it
delegate
nearly everything to the fixnum current value, which really is a value
object.
So it isn't a value, but it delegates to one.

Basically, anything that has internal state to keep track of which is
not part
of how you use it strikes me as not really being a value, even if it
looks
like one from the outside.

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.
That said!

I am thinking that perhaps it should *present* as immutable -- that is,
I
should not define john.strength = 15 in any way, but rather, if you want
to
ask it to change its value, you have to send it messages. So instead of
john.strength += 1, you'd write john.strength.adjust_by(1).

No need. The former is a lot friendlier. You can always redefine
Character#strength= .
Hmm. I suppose the thing, then, would be to simply not provide a
strength=
method for the things that have stats.

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

Remember, one of the nice things about OO programming is that interface
and implementation needn't resemble each other in the slightest.

Best,
 
S

Seebs

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.
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?
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.
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
 
M

Marnen Laibow-Koser

Seebs said:
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.

a = Cash.new(Array.new(2, Banknote.new(5, :usd)))
b = Cash.new([Banknote.new(10, :usd))
a.to_money = b.to_money
a.banknotes != b.banknotes
a ?== b # depends on your definition of equality.
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.

That would be the appropriate solution if you never wanted to expose the
Stat object.
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.

Then your clients will know that it's a Stat object, and expect to call
to_i on it. I don't see anything wrong with that; you can overload Stat
+ Fixnum and so on as we discussed earlier in this thread.

But if you really want automatic, you can have it:
class Stat
def to_int
self.to_i
end
end

(According to http://www.rubyfleebie.com/to_i-vs-to_int/ , to_int is
called automatically as necessary and will make your class act as an
Integer. to_i, of course, will not do that.)

[...]
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.

For your use case, you're probably right. For real value objects, it's
a different story.
None of those classes are complicated trees containing five or ten or
thirty other objects, though, are they?

Fixnum probably isn't. Date probably contains about 5 other objects
(totally guessing here -- haven't looked at the implementation). The
BigDecimal library I wrote for Rubinius contained about 3-5 other
objects, but I don't know if MRI or JRuby does it the same way. The
Address class I wrote for Quorum ( http://quorum2.sourceforge.net )
contains about 6 fields in an immutable value object. Of course, I
rarely only need to modify one field in someone's address.
Modifier is a class too, which can handle things like counting down its
duration, etcetera.

And a stat has a handful of them.

OK...*these* are your value object candidates, perhaps.
It seems to me that the knowledge that a modifier expires is best
handled
by embedding it in the modifier.

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

If it's not accurate, then there's no point keeping 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.

That depends. A good argument could be made for having the Character
store its own history, and just having the Stats be values.

In either case, though, for this sort of functionality you don't
necessarily need a full-fledged history. All you need is something like
class Stat
def value=(new_value)
@max_value = [new_value, @value].max
@value = new value
end
end
If you're ambitious, you could put together something like
ActiveRecord's before_save (which is probably what I'd use for this in a
Rails app).
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.

Well, then you'll run into the design issues when you're ready for them.
For now, though, and at every future step, try to
http://c2.com/cgi/wiki?DoTheSimplestThingThatCouldPossiblyWork -- that
is, work with the state of the app *at that time*. I know premature
generalization is tempting, but it's not usually a good idea.
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.

Right. It's now clear that your stat object is too complex to be a
simple immutable value object, although some of its components might
well be.
I think so.


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.

I don't think delegation will work here -- for one thing, you may not
need to store the total value in any actual instance variable. Using
to_int or possibly coerce will work much better.

Best,
 
S

Seebs

Then your clients will know that it's a Stat object, and expect to call
to_i on it.

Except that it's extremely annoying to have an object where, out of five
hundred uses, four hundred and ninety six need an extra ".to_i".
I don't see anything wrong with that; you can overload Stat
+ Fixnum and so on as we discussed earlier in this thread.
Yeah.

But if you really want automatic, you can have it:
class Stat
def to_int
self.to_i
end
end
(According to http://www.rubyfleebie.com/to_i-vs-to_int/ , to_int is
called automatically as necessary and will make your class act as an
Integer. to_i, of course, will not do that.)

This turns out not to quite be the case, in experiments. If I do that,
it works most of the time, but as an example:
john.str + john.dex + john.con
doesn't work, because it can't figure out that ANY of them should be
integers. I also end up having to define a bunch of additional operators;
for instance, if I want to be able to write "john.str + 3", I have to define
+ for Stat, even though "3 + john.str" would probably do the right thing.
OK...*these* are your value object candidates, perhaps.

Hmm. Well, probably not -- modifiers have internal state (duration, which I
was thinking to indicate as a turn counter) which they want to update. Maybe.
I could make them into values perhaps if I, say, calculated their expiration
rather than their duration. Then, if you refresh a modifier, you make a new
one with a later expiration. Hmm.
If it's not accurate, then there's no point keeping a record.

There can be; see below.
In either case, though, for this sort of functionality you don't
necessarily need a full-fledged history.

Right. I pretty much meant a mini-history like that.
Right. It's now clear that your stat object is too complex to be a
simple immutable value object, although some of its components might
well be.

I'm sort of liking the idea of making modifiers into immutable values. It
would actually make life simpler, I think.
I don't think delegation will work here -- for one thing, you may not
need to store the total value in any actual instance variable. Using
to_int or possibly coerce will work much better.

Delegation empirically *does* work, although it may not be the most efficient
or best choice of how to do things. Stashing the total value in an instance
variable may be useful anyway; these things get looked up pretty often.

-s
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top