2
\$\begingroup\$

I need to return private collection and

  1. avoid calling code modifying them
  2. avoiding copying overhead if possible

So I thought of returning copy-on-write arrays, hashes, and sets.

I couldn't find any implementations of these, so I tried my own.

require 'delegate'
require 'set'

module Cow end

class Cow::Base < SimpleDelegator
  def initialize(obj)
    super
    obj.freeze
    @copied = false
  end

  def method_missing(*args, &block)
    if @copied
      super
    else
      begin
        super
      rescue RuntimeError => e
        if e.to_s.include? 'can\'t modify frozen'
          __setobj__ __getobj__.dup
          @copied = true
          super
        else
          raise
        end
      end
    end
  end
end

module CowClass
  def cow
    Cow::Base.new self
  end
end

class Array
  include CowClass
end

class Hash
  include CowClass
end

class Set
  include CowClass
end

And then used like

a = [1, 2, 3]
b = a.cow
b << 4
puts a.join ' ' # 1 2 3
puts b.join ' ' # 1 2 3 4

Suggestions? Applause? Boos?

Thread-safety is a non-goal.

\$\endgroup\$
  • \$\begingroup\$ When I run your code, I get a and b are both [1, 2, 3, 4] \$\endgroup\$ – Zack Oct 11 '16 at 14:48
  • \$\begingroup\$ @Zack, I forgot the freeze. FIxed. \$\endgroup\$ – Paul Draper Oct 11 '16 at 19:07
  • 1
    \$\begingroup\$ This strategy seems likely to cause confusion, as you're changing the semantics of operators like <<. I think it would be simpler and better to use immutable objects, and force your clients to use non-mutating methods like concat when they want to "write". That's just better ruby practice anyway. And you can avoid the whole issue.... \$\endgroup\$ – Jonah Oct 15 '16 at 21:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.