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 agree it looks like premature optimisation, but it is not only meant as an optimisation. It is also about having an persistent anonymous object.
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
I'd like to have "once" in Ruby, but in a totally different way:
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
I've done an implementation for this other kind of "once" but I call it cacheable. See message on ruby-talk. -- joar
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:
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:
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...
works only for one once at one line.
-- Matthias Georgi
I agree it looks like premature optimisation, but it is not only meant as an optimisation. It is also about having an persistent anonymous object.
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:
...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:
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.
Probably not really useful for you, but basically the question to ask is, if LISP like macros should be added to Ruby ...
-- Matthias Georgi
I'd like to have "once" in Ruby, but in a totally different way:
It's coded acting like this or similar somewhere in the Pickaxe book, but I don't know anymore where exactly. -- MalteI've done an implementation for this other kind of "once" but I call it cacheable. See message on ruby-talk. -- joar