An idiom I like... modifiable defaults

Discussion in 'Ruby' started by Hal Fulton, May 9, 2005.

  1. Hal Fulton

    Hal Fulton Guest

    Just thought I'd share a little concept that I find
    useful. Your comments are welcome.

    Sometimes objects are created with certain defaults.
    One way to override them is with default values in
    the constructor (and often corresponding writer methods).

    But sometimes I "don't like" the default and want to
    change it (for this program/session).

    Often I use class-level accessors for that purpose.

    Here's a contrived example...


    Cheers,
    Hal


    class Text

    class << self
    attr_accessor :color
    Text.color = "black"
    end

    attr_accessor :color

    def initialize(txt, color="black")
    # Hint: You can improve this further by saying
    # def initialize(txt, color=Text.color)
    puts "#{color} text..."
    end
    end


    # The old way...

    a = Text.new("some") # black
    b = Text.new("random","blue") # blue
    c = Text.new("text") # black
    c.color = "blue" # but now it's blue

    # The new way...

    Text.color = "blue"

    e = Text.new("Ruby is cool") # blue
    f = Text.new("as dry ice") # blue
     
    Hal Fulton, May 9, 2005
    #1
    1. Advertisements

  2. Thinking in code - some other approach:

    def hash_replace(hash, replacement)
    tmp = hash.dup

    begin
    hash.update replacement
    return yield
    ensure
    hash.clear
    hash.update tmp
    end
    end

    {1=>2, :foo=>"bar"}
    => nil{1=>2, :foo=>"replaced"}
    => nil{1=>2, :foo=>"bar"}
    => nil

    Hm...

    Kind regards

    robert
     
    Robert Klemme, May 9, 2005
    #2
    1. Advertisements

  3. Hi,
    I quite like this, but wonder if it works with inheritance?

    On a related note, it seems a lot of people (you, me, Ara Howard,
    probably many others) are coming up with these configuration idioms,
    maybe we could find a way that fulfills most needs (and perhaps even
    try to get it into ruby core)?

    Here's a quick list of features for discussion, feel free to add your
    own:
    - named arguments (ie. hash argument way)
    - auto-assigning configuration vars to instance variables
    - works with inheritance (this is probably the most difficult -- and
    useful -- one)
    - auto-generated accessors
    - configuration by block
    - maybe some way to co-operate with the regular positional params


    Would look something like this in use:

    class Image
    include TheAutoConfigModule
    config_accessor{
    name # defaults to nil
    width = 100
    height = 100
    depth = 32
    }
    def bytes
    @width * @height * @depth / 8
    end
    end

    class BWImage < Image
    config_accessor{ depth = 8 }
    end

    i = Image.new:)height => 200, :depth => 16)
    i.width == 100 and i.height == 200 and i.depth == 16
    #=> true
    i.name
    #=> nil
    i.bytes
    #=> 40000

    bwi = BWImage.new( 'holiday shots', 10, 10 )
    bwi.width == 10 and bwi.height == 10 and bwi.depth == 8
    #=> true
    bwi.name
    #=> 'holiday shots'
    bwi.bytes
    #=> 100

    Image.config
    #=> {:name => nil, :width => 100, :height => 100, :depth => 32}

    BWImage.config.height = 200
    BWImage.config
    #=> {:name => nil, :width => 100, :height => 200, :depth => 8}

    Thoughts?


    Cheers,
    Ilmari
     
    Ilmari Heikkinen, May 9, 2005
    #3
  4. Hal Fulton

    Ara.T.Howard Guest

    i do this so much i built it in to my new traits lib (currently being
    updated). works like:

    harp:~ > cat a.rb
    require 'traits'

    class Text
    class_trait 'color' => 'black'
    trait 'color'

    def initialize opts = {}
    color( opts['color'] || opts[:color] || Text.color)
    end
    end

    Text::color = 'blue'
    p Text::new


    harp:~ > ruby a.rb
    #<Text:0xb75bb744 @color="blue">

    i generally also allow modifing classes via the environment, eg:

    class Text
    class_trait 'color' => (ENV['TEXT_COLOR'] || 'black')
    trait 'color'
    def initialize opts = {}
    color( opts['color'] || opts[:color] || Text.color)
    end
    end

    i'm using this in all cases except true constants - like PI.

    cheers.


    -a
    --
    ===============================================================================
    | email :: ara [dot] t [dot] howard [at] noaa [dot] gov
    | phone :: 303.497.6469
    | renunciation is not getting rid of the things of this world, but accepting
    | that they pass away. --aitken roshi
    ===============================================================================
     
    Ara.T.Howard, May 9, 2005
    #4
  5. Hal Fulton

    Glenn Parker Guest

    Not very thread-safe, at least not in your example's implementation.
     
    Glenn Parker, May 9, 2005
    #5
  6. Hal Fulton

    Ara.T.Howard Guest

    even this is not:

    class C
    attr_accessor :x
    end

    unless you make it.

    FYI.

    -a
    --
    ===============================================================================
    | email :: ara [dot] t [dot] howard [at] noaa [dot] gov
    | phone :: 303.497.6469
    | renunciation is not getting rid of the things of this world, but accepting
    | that they pass away. --aitken roshi
    ===============================================================================
     
    Ara.T.Howard, May 9, 2005
    #6
  7. Hal Fulton

    Glenn Parker Guest

    Yes, if instances of class C are to be accessed from multiple threads,
    then some extra care is needed, but that depends very much on the actual
    uses of the class. A lot of classes never need to worry about it, even
    in highly threaded applications.

    In the OP's code, it's clear that all threads using class Text will be
    in contention for Text.color. In that style of interface, a per-thread
    variable is practically mandatory.
     
    Glenn Parker, May 9, 2005
    #7
  8. Hal Fulton

    Hal Fulton Guest

    No. I suppose if I were using this with threads I would
    wrap the thread-safety code around it rather than hiding
    it inside.


    Hal
     
    Hal Fulton, May 9, 2005
    #8
  9. This is actually my primary use case for class variables (@@var) -
    inheriting configuration defaults down a class tree.

    martin
     
    Martin DeMello, May 10, 2005
    #9
  10. The problem with class vars is that changing them in the subclass
    affects the superclass aswell.

    class A
    @@foo = "A"
    def foo; @@foo; end
    end

    class B < A
    @@foo = "B"
    end

    A.new.foo
    => "B"
     
    Ilmari Heikkinen, May 10, 2005
    #10
  11. Yes, but you're the programmer - simply don't change them in the
    subclass.

    martin
     
    Martin DeMello, May 10, 2005
    #11
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.