2008/1/31, Leif Eriksen <leif.eriksen / bigpond.com>:
> Some background - bear with me
>
> So I'm writing my first little library (module) in ruby, for a
> play-project in rails.
>
> The library is for color management related functions, and its first
> method is to
> provide a color 'gradient', which I use for images where the colors smoothly
> change from a to b in as many steps as I require
>
> for example, to get from html rgb color #010101 to #444444 in 5 'steps',
> we would do
>
> ColorManagement.gradient(#010101, #444444, 5) # will used named params
> in version 2!
>
> and it would return
>
> ["#010101", "#0e0e0e", "#1b1b1b", "#282828", "#353535", "#444444"]
>
> The method supports leading '0x' chars as well, and prepends the leading
> chars (if any) to the
> entries in the result array.
>
> And this is all cool, until I did this
>
> start = '#010101'
> finish = '#444444'
> ColorGradient.gradient(start, finish , 5)
> pp start "#{start}"
> pp "finish #{finish }"
>
> and I see this
>
> "start 010101"
> "finish 444444"
>
> The '#''s are gone. Internally the library is stripping these off to make
> splitting into RGB channels easier, and it does the stripping like this
>
>   def self.gradient(hex_start="000000", hex_end="FFFFFF", steps=256)
>       [hex_start,hex_end].each do |hex|
>          hex.sub!(/^(0[xX]|#)/) do |match|
>            match = '' # remove any leading # or Ox
>
> So the strings that are passed in are permanently munged by the sub! .
> OK I get that, and to solve it I did this
>
>   def self.gradient(param_start="000000", param_end="FFFFFF", steps=256)
>     #make local copies of parameters
>     hex_start = String.new(param_start)
>     hex_end   = String.new(param_end)
>
>       [hex_start,hex_end].each do |hex|
>         hex.sub!(/^(0[xX]|#)/) do |match|
>         ...
>
> So in effect I copy the parameter strings into local vars, to avoid
> munging what the user passes me and annoying them.
>
> But is this the ruby way ? Is this the idiomatic way to avoid
> side-effects on objects passed in as parameters ?

I'd say, this is generally considered bad OO.  A better solution would
be to create a class Color that provides methods to do all the
conversions.  E.g.

Color = Struct.new :red, :green, :blue do

  # will accept either
  # a single string with hex number
  # a single fixnum (0x000000 - 0xFFFFFF, larger values are truncated)
  # three fixnums
  # three strings
  def initialize(*a)
    case a.length
      when 3
        self.red,
        self.green,
        self.blue = a.map {|v| arg2bin(v) % 0x100}
      when 1
        tmp = arg2bin a.first
        raise ArgumentError, "Negative value" if tmp < 0
        self.red = (tmp >> 16) % 0x100
        self.green = (tmp >> 8) % 0x100
        self.blue = tmp % 0x100
      else
        raise ArgumentError,
          "Need either a single string with hex number, a single
Fixnum or three separate Fixnums or hex Strings"
    end
  end

  def to_hex
    sprintf "%02x%02x%02x", red, green, blue
  end

  def to_s
    sprintf "#%02x%02x%02x", red, green, blue
  end

  def &(color)
    Color.new red & color.red,
      green & color.green,
      blue & color.blue
  end

  def |(color)
    Color.new red | color.red,
      green | color.green,
      blue | color.blue
  end

private
  def arg2bin(a)
    case a
      when Fixnum
        a
      when String
        a.to_i 16
    end
  end
end

This is just a sample implementation.  You might as well change
internal representation to a single Fixnum etc.  Then add methods as
you need them.

Kind regards

robert

-- 
use.inject do |as, often| as.you_can - without end