Editing Topic: RCR248 Project: RCR | RCRchive home

RCR 248: 'once' operator to evaluate an expression only once

submitted by Eludias on Sun Apr 18 2004 05:37:40 AM -0700

Status: pending


Abstract

Objects which are constant during a script only need to be evaluated once. The best place for this is at the object definition itself. This saves time and/or memory, and keeps local objects local.

Problem

When a constant object is reused a lot of times in indepenent contexts, a place to cache this object must be created to be able to share this constant object. For regular expressions this is already arranged with the 'once' modifier: %r{expression}o compiles the regular expression only once, and next time the cached version is used. This is also useful for other objects because of space and/or time concerns: for example, a string can be shared. This can be worked around by using named objects which act as the cache in the class or global scope, but this is ugly.

Proposal

Add the 'once' keyword/method to indicate a part of the parse-tree which should be evaluated only once just like in the regular expression case. For this, we need something with a local scope without having a name: an anonymous instance (just like the already existing anonymous classes). The 'once' keyword would take one expression which should be evaluated at most once. For example,
%r{something}o
is 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

Analysis

This problem requires a language-level change since only the parser can cache depending on the position in the script and one cannot pass a parsed expression without evaluating it first in the given context. The infrastructure is already in place since it uses the same construct as is already in use for the regular expressions. The 'once' operator is a small add-on which can be used in a lot of places to optimise a ruby script.

Implementation

Implementation is not possible in pure Ruby, although in pseudo-code it would be (non-optimal):
  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


Back to RCRchive.


RCR Submission page and RCRchive powered by Ruby, Apache, RuWiki (modified), and RubLog