Newbie question: Defining a numeric type

Discussion in 'Ruby' started by Seebs, Nov 15, 2009.

  1. Seebs

    Seebs Guest

    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
    --
    Copyright 2009, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 15, 2009
    #1
    1. Advertising

  2. Seebs

    Mario Camou Guest

    Re: Newbie question: Defining a numeric type

    Have a look at Forwardable

    On Monday, November 16, 2009, Seebs <> wrote:
    > 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
    > --
    > Copyright 2009, all wrongs reversed. =A0Peter Seebach / usenet-nospam@see=

    bs.net
    > http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    > http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    >
    >


    --=20
    -Mario.

    --
    I want to change the world but they won't give me the source code.
    Mario Camou, Nov 16, 2009
    #2
    1. Advertising

  3. Seebs wrote:
    > 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,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 16, 2009
    #3
  4. Seebs wrote:
    > 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,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 16, 2009
    #4
  5. Seebs

    Seebs Guest

    On 2009-11-16, Marnen Laibow-Koser <> wrote:
    > 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
    --
    Copyright 2009, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 16, 2009
    #5
  6. Seebs wrote:
    > On 2009-11-16, Marnen Laibow-Koser <> wrote:
    >> 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.


    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.

    >
    > -s


    Best,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 16, 2009
    #6
  7. Seebs wrote:
    > 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.
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Nov 16, 2009
    #7
  8. Seebs wrote:
    > On 2009-11-16, Marnen Laibow-Koser <> wrote:
    >> Seebs wrote:
    >>> 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.

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

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

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

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

    >
    > -s


    Best,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 16, 2009
    #8
  9. Seebs

    Seebs Guest

    On 2009-11-16, Marnen Laibow-Koser <> wrote:
    >> (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.


    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
    --
    Copyright 2009, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 16, 2009
    #9
  10. Seebs

    Seebs Guest

    On 2009-11-16, Marnen Laibow-Koser <> wrote:
    >> (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.


    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
    --
    Copyright 2009, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 16, 2009
    #10
  11. Seebs wrote:
    > On 2009-11-16, Marnen Laibow-Koser <> wrote:
    >>> (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.)

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


    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?

    > -s


    Best,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 16, 2009
    #11
  12. Seebs

    Seebs Guest

    On 2009-11-16, Marnen Laibow-Koser <> wrote:
    > 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
    --
    Copyright 2009, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 16, 2009
    #12
  13. Seebs wrote:
    > On 2009-11-16, Marnen Laibow-Koser <> wrote:
    >> 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.


    So would I.

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


    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?

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


    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,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 16, 2009
    #13
  14. Seebs

    Seebs Guest

    On 2009-11-16, Marnen Laibow-Koser <> wrote:
    > 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.

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


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

    >> 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. :)


    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.

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


    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
    --
    Copyright 2009, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 17, 2009
    #14
  15. Seebs wrote:
    > On 2009-11-16, Marnen Laibow-Koser <> wrote:
    >> 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.


    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.

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

    >
    > "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.
    >
    >>> 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. :)

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

    >
    > -s


    Best,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 17, 2009
    #15
  16. Seebs

    Seebs Guest

    On 2009-11-17, Marnen Laibow-Koser <> wrote:
    >== 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.

    >> 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!


    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
    --
    Copyright 2009, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 17, 2009
    #16
  17. Seebs wrote:
    [...]
    >> 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.


    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?

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


    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.

    >
    >>> 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!

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

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


    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!

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


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


    Best,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 17, 2009
    #17
  18. Seebs

    Seebs Guest

    On 2009-11-17, Marnen Laibow-Koser <> 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 /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 17, 2009
    #18
  19. Seebs wrote:
    > On 2009-11-17, Marnen Laibow-Koser <> 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.


    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.

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


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

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


    For your use case, you're probably right. For real value objects, it's
    a different story.

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


    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.

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


    OK...*these* are your value object candidates, perhaps.

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


    It might be.

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


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

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


    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.

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


    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.

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


    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.

    >
    > -s


    Best,
    --
    Marnen Laibow-Koser
    http://www.marnen.org

    --
    Posted via http://www.ruby-forum.com/.
    Marnen Laibow-Koser, Nov 17, 2009
    #19
  20. Seebs

    Seebs Guest

    On 2009-11-17, Marnen Laibow-Koser <> wrote:
    >> 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.


    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.

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


    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.

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


    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
    --
    Copyright 2009, all wrongs reversed. Peter Seebach /
    http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
    http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
    Seebs, Nov 17, 2009
    #20
    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. johny smith
    Replies:
    8
    Views:
    416
    Peter Koch Larsen
    Jul 2, 2004
  2. Replies:
    5
    Views:
    933
    X-Centric
    Jun 30, 2005
  3. darrel
    Replies:
    4
    Views:
    817
    darrel
    Jul 19, 2007
  4. jobs

    int to numeric numeric(18,2) ?

    jobs, Jul 21, 2007, in forum: ASP .Net
    Replies:
    2
    Views:
    959
    =?ISO-8859-1?Q?G=F6ran_Andersson?=
    Jul 22, 2007
  5. Urs Thuermann
    Replies:
    6
    Views:
    463
    Urs Thuermann
    Nov 4, 2011
Loading...

Share This Page