%r{something}ois equal to
    once(%r{something}). Space-saving example:
    
  class LittleObject
    attr_reader :aspect1
    def initialize(aspect1)
      @aspect1 = once("defaultValue")
      @aspect1 = aspect1 if aspect1 != nil
    end
  end
When thousands of LittleObjects are created with a default 'aspect1', this saves space since all instances of LittleObject which do not change aspect1 still refer to the default (unnamed) value. The current (inelegant) solution would be:
    
      ...
      DEFAULTVALUE = "defaultValue" if not defined? DEFAULTVALUE
      @aspect1 = DEFAULTVALUE
      ...
...but this requires given something a name which does not need a name. Time saving example:
    
   def logLine(line)
     puts "#{once(`uname --nodename`)} #{line}"
   end
This will only call 'uname' once. Currently this can be implemented as:
    
   def logLine(line)
     $nodename = `uname --nodename` if not $nodename
     puts "#{$nodename} #{line}"
   end
..which isn't too bad, but creates a variable with a much larger scope (global or a constant) than needed. In this last example, the naming is not a problem. Code saving example:
    
   class StringCache
     def Cached(str)
       cache = once({})
       cache[str] = str if not cache[str]
       cache[str]
     end
   end
  
  def once(expression)
    $onceCache = {} if not $onceCache
    res = $onceCache[[expression, stack[-1]]
    if not res
      $onceCache[[expression, stack[-1]] = eval(expression, stack[-1].context)
    else
      res
    end
  end
  I know this already exists in the case of regular expressions, but I have to say I'd rather not see 'once' generally in the language. I like my high-level languages high-level :-) The idea of adding, in effect, compiler hints to Ruby doesn't appeal to me.
Also, I think you can achieve all of what you need already in various ways -- sometimes, admittedly, more verbose than "once", but not unreasonably so. Class objects are a good place to stash things. For example:
class A
  def self.default_a1
    @a1 ||= "default value"
  end
  attr_reader :aspect1
  def initialize(aspect1=nil)
    @aspect1 = aspect1 || A::default_a1
  end
  end
I actually prefer having this laid out programmatically like this, though tastes may vary.
-- David Black
Is this some feature from perl ? In contrast to perl, it's quite good to have a meta programming language like ruby. Just a few lines:
def once(exp,file=nil,line=nil,bind=nil)
  $once ||= {}
  $once[[exp,file,line]] ||= eval(exp,bind)
  end
once '/regex(expression)?/'
class A
  def A.methods
    once('methods', __FILE__, __LINE__, binding)
  end
  end
But I think, you don't need this very often. Ruby has limited meta access compared to LISP and this is a good thing. Furthermore Perfomance tuning complicates the language.
Besides, two binding objects created at the same place are not equal. I think, it would be better if binding == binding would result in true. Any opinions ?
-- Matthias Georgi
Sorry, I'm losing the flow here. Is what "some feature from Perl"?
-- David Black
Was not important, I imagined, "evaluating once" was a Perl feature, but I was mistaken. Now a better once...
def once
  $once ||= {}
  $once[caller.first] ||= yield
  end
def test_once
  world = :world
  once { "Hello #{world}" }
  end
test_once == test_once => true
works only for one once at one line.
-- Matthias Georgi
I didn't know the '||=' trick mentioned above, and looks quite useful in itself: one could see the once() as a way to indicate what you're doing instead of obfuscating it with '||='; a '$cache = once({})' looks more clear to me than a '$cache ||= {}'. [However, I think I'll just over turn to the dark side and will use the '||=' construct myself :-]
However, for optimisation purposes (indeed, not the most important one), the last solution does not suffice time-wise:
def once; $once ||= {}; $once[caller.first] ||= yield; end
class SmallObject
    def initialize
    $myValue ||= "My value"
    $myValue = once { "My value" }; @val = $myValue
  end
  end
sos = []; t = Time.now for i in 1..1000000; sos.push(SmallObject.new); end puts Time.now - t
...takes 10x (measured) as much time than the '||=' version, so that's not really an option for a lot of small objects, because of calling a method, constructing an array and throwing it all away, yielding, etc. [Yeah, I know, micro-benchmarking, don't do it, etc...]
Therefore I proposed to add it to the language: It is simple to implement, it's clear what the user meant with it and it's an optimisation when implemented in the language itself.
To see that it is simple to implement, see eval.c for the current implementation of the regular-expression case. The parse-tree is changed during the first evaluation so the next time the literal can be used:
              case NODE_DREGX_ONCE:     /* regexp expand once */
                result = rb_reg_new(RSTRING(str)->ptr, RSTRING(str)->len,
                                    node->nd_cflag);
                nd_set_type(node, NODE_LIT);
                node->nd_lit = result;
                break;
  This is also the right place: a 'once' should not be dependent on a line-number, but the place in the code, which equals the location in the parse tree (which is walked in eval.c).
And about bindings not being equal: {}.eql?({}) is already false (quite logical). {} == {} is true indeed, but the same holds for once() expressions if the objects represents the same thing.
But still thanks for the '||=' operator! (...time to impress my friends with it ;-)
Rutger.
||= is not ugly, but a common ruby idiom. After benchmarking, I was tempted to find a solution and finally I've got a little hack.
$once = {}
class SmallObject1
  def initialize
    @val = once("My value")
  end
  end
class SmallObject2
  def initialize    
    @val = ($myValue ||= "My value")
  end
  end
eval(File.read(__FILE__).gsub(/eval/,'#').gsub(/once\((.*?)\)/, '($once[__LINE__] ||= \1)'))
require 'benchmark' include Benchmark
bm do |x|
  x.report { 100000.times { SmallObject1.new } }
  x.report { 100000.times { SmallObject2.new } }
  end
Probably not really useful for you, but basically the question to ask is, if LISP like macros should be added to Ruby ...
-- Matthias Georgi
  class MyClass
     def do_heavy_calculation
          # some calculation finally returning something
     end
     once :do_heavy_calculation
   end   obj = MyClass.new
   a = obj.do_heavy_calculation # Here the value is calculated and stored
                                # somewhere in obj
   b = obj.do_heavy_calculation # Here the value is simply looked up 
  It's coded acting like this or similar somewhere in the Pickaxe book, but I don't know anymore where exactly. -- Malte
Back to RCRchive.
RCR Submission page and RCRchive powered by Ruby, Apache, RuWiki (modified), and RubLog