[BUG] Small bugs with frozen things

F

Florian Gross

Moin!

Some oddities I noticed:

irb(main):034:0> File.open("C:/autoexec.bat").freeze.inspect
TypeError: can't modify frozen File # inspect doesn't modify
from (irb):34:in `inspect'
# This one also happens for File#path etc.


irb(main):063:0> Dir.new(".").freeze.send:)initialize, "C:/").read
=> "katapult.zip" # Initialize executed despite Dir being frozen


irb(main):097:0> Class.new.freeze.to_s
TypeError: can't modify frozen object # to_s doesn't modify
from (irb):97:in `to_s'


Regards,
Florian Gross
 
R

Robert Klemme

Florian Gross said:

Moin moin!
Some oddities I noticed:

irb(main):034:0> File.open("C:/autoexec.bat").freeze.inspect
TypeError: can't modify frozen File # inspect doesn't modify
from (irb):34:in `inspect'
# This one also happens for File#path etc.

IMHO an IO instance can't really be frozen in a meaningful way because it
must maintain changing state (namely the stream position). Maybe
IO#freeze should throw an exception ("illegal operation", "cannot freeze"
or similar).
irb(main):063:0> Dir.new(".").freeze.send:)initialize, "C:/").read
=> "katapult.zip" # Initialize executed despite Dir being frozen

I would not regard this a bug since initialize is expected to change the
instance, isn't it?
irb(main):097:0> Class.new.freeze.to_s
TypeError: can't modify frozen object # to_s doesn't modify
from (irb):97:in `to_s'

That's really surprising. Maybe it's due to lazy initialization of the
class's name.

Btw, do people frequently use #freeze?

Kind regards

robert
 
F

Florian Gross

Robert said:
Moin moin!

Moin moin moin!
IMHO an IO instance can't really be frozen in a meaningful way because it
must maintain changing state (namely the stream position). Maybe
IO#freeze should throw an exception ("illegal operation", "cannot freeze"
or similar).

Yup, and there definitely shouldn't be errors when trying to access the
path of a frozen File etc. :)

(But IMHO an IO instance can also be frozen -- the internal state
doesn't have to be frozen just because the Object is.)
I would not regard this a bug since initialize is expected to change the
instance, isn't it?

Well, [1, 2, 3].freeze.send:)initialize, 3) { rand } raises an error. :)
That's really surprising. Maybe it's due to lazy initialization of the
class's name.

Still, would be part of the internal state -- a frozen Object should
still be able to change its non-exposed state IMHO. (Which means that
freezing is an operation that is only visible to the outside, not the
inside)
Btw, do people frequently use #freeze?

I dunno. :)
Kind regards
robert

More regards,
Florian Gross
 
R

Robert Klemme

Florian Gross said:
Moin moin moin!

Moin ** 4!
:)
Yup, and there definitely shouldn't be errors when trying to access the
path of a frozen File etc. :)

(But IMHO an IO instance can also be frozen -- the internal state
doesn't have to be frozen just because the Object is.)

Well, technically you are right. However, I'd expect #freeze to prevent
further modifications of the instance. In case of a stream that would
include reading and closing - which isn't really useful.
I would not regard this a bug since initialize is expected to change the
instance, isn't it?

Well, [1, 2, 3].freeze.send:)initialize, 3) { rand } raises an error. :)

Oh, it's the other way round! Sorry. Just forget my remark.
Still, would be part of the internal state -- a frozen Object should
still be able to change its non-exposed state IMHO. (Which means that
freezing is an operation that is only visible to the outside, not the
inside)

Well, there's much debate (see also C++ with 'const' and 'mutable'). IMHO
it's quite difficult to state what constness means in the general case.
I guess that's why this is used rather sparingly.

Well... I don't either. I can only say that I use it very sparingly.

Cheers

robert
 
G

Gavin Sinclair

Btw, do people frequently use #freeze?

I use it occasionally.

BTW, Date.today.freeze.to_s gives an error too. When I raised it on
ruby-core Matz agreed it's an issue but didn't really know off hand
how to fix it. (The problem is caused by caclulations on instance
vars for caching - I think.)

And I guess one could write their own #to_s or #inspect in particular
cases if it were a problem for them.

Gavin
 
R

Robert Klemme

Gavin Sinclair said:
I use it occasionally.

Ah! *You* are the one... :))
BTW, Date.today.freeze.to_s gives an error too. When I raised it on
ruby-core Matz agreed it's an issue but didn't really know off hand
how to fix it. (The problem is caused by caclulations on instance
vars for caching - I think.)

Most likely.
And I guess one could write their own #to_s or #inspect in particular
cases if it were a problem for them.

The cleanest might be to introduce something similar to C++'s keyword
'mutable', which allows instance variables to be changed even if the
container is const (aka frozen). But I don't like this approach because
things soon get messy. Constness is difficult to get right (not so much
for the language but rather for users of the language) IMHO.

Alternative solutions could make use of WeakReference somehow, although
this is just a faint idea - nothing concrete.

Kind regards

robert
 
N

nobu.nokada

Moin,

At Tue, 15 Jun 2004 17:08:36 +0900,
Florian Gross wrote in [ruby-talk:103646]:
irb(main):034:0> File.open("C:/autoexec.bat").freeze.inspect
TypeError: can't modify frozen File # inspect doesn't modify
from (irb):34:in `inspect'
# This one also happens for File#path etc.

IO#inspect also fails in secure mode (when $SAFE >= 4). I'm
not sure whether it should be possible or not.
irb(main):063:0> Dir.new(".").freeze.send:)initialize, "C:/").read
=> "katapult.zip" # Initialize executed despite Dir being frozen

This is simple to fix, adding rb_check_frozen() to every
initialize methods... but tiresome.
irb(main):097:0> Class.new.freeze.to_s
TypeError: can't modify frozen object # to_s doesn't modify
from (irb):97:in `to_s'

Seems wrong.


Index: variable.c
===================================================================
RCS file: /var/cvs/src/ruby/variable.c,v
retrieving revision 1.113
diff -u -2 -p -r1.113 variable.c
--- variable.c 14 May 2004 16:39:15 -0000 1.113
+++ variable.c 16 Jun 2004 13:35:57 -0000
@@ -23,4 +23,7 @@ st_table *rb_class_tbl;
static ID autoload, classpath, tmp_classpath;

+#define ENSURE_IV_TBL(tbl) \
+ ((tbl) ? (tbl) : ((tbl) = st_init_numtable(), (st_table *)0))
+
void
Init_var_tables()
@@ -131,7 +134,5 @@ find_class_path(klass)
}
if (arg.path) {
- if (!ROBJECT(klass)->iv_tbl) {
- ROBJECT(klass)->iv_tbl = st_init_numtable();
- }
+ ENSURE_IV_TBL(ROBJECT(klass)->iv_tbl);
st_insert(ROBJECT(klass)->iv_tbl, classpath, arg.path);
st_delete(RCLASS(klass)->iv_tbl, &tmp_classpath, 0);
@@ -209,5 +210,6 @@ rb_class_path(klass)
sprintf(RSTRING(path)->ptr, "#<%s:0x%lx>", s, klass);
RSTRING(path)->len = strlen(RSTRING(path)->ptr);
- rb_ivar_set(klass, tmp_classpath, path);
+ ENSURE_IV_TBL(ROBJECT(klass)->iv_tbl);
+ st_insert(ROBJECT(klass)->iv_tbl, tmp_classpath, path);

return path;
@@ -873,7 +875,5 @@ generic_ivar_set(obj, id, val)
special_generic_ivar = 1;
}
- if (!generic_iv_tbl) {
- generic_iv_tbl = st_init_numtable();
- }
+ ENSURE_IV_TBL(generic_iv_tbl);

if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) {
@@ -1050,5 +1050,5 @@ rb_ivar_set(obj, id, val)
case T_CLASS:
case T_MODULE:
- if (!ROBJECT(obj)->iv_tbl) ROBJECT(obj)->iv_tbl = st_init_numtable();
+ ENSURE_IV_TBL(ROBJECT(obj)->iv_tbl);
st_insert(ROBJECT(obj)->iv_tbl, id, val);
break;
@@ -1510,7 +1510,5 @@ rb_mod_const_at(mod, data)
{
st_table *tbl = data;
- if (!tbl) {
- tbl = st_init_numtable();
- }
+ ENSURE_IV_TBL(tbl);
if (RCLASS(mod)->iv_tbl) {
st_foreach(RCLASS(mod)->iv_tbl, sv_i, (st_data_t)tbl);
@@ -1645,8 +1643,5 @@ mod_av_set(klass, id, val, isconst)
}
}
- if (!RCLASS(klass)->iv_tbl) {
- RCLASS(klass)->iv_tbl = st_init_numtable();
- }
- else if (isconst) {
+ if (ENSURE_IV_TBL(RCLASS(klass)->iv_tbl) && isconst) {
VALUE value = Qfalse;
 
G

Gavin Sinclair

The cleanest might be to introduce something similar to C++'s keyword
'mutable', which allows instance variables to be changed even if the
container is const (aka frozen). But I don't like this approach because
things soon get messy. Constness is difficult to get right (not so much
for the language but rather for users of the language) IMHO.

I had a thought about a solution today. Let's just say that Date#to_s
is broken when the object is frozen because it's modifying internal
state for caching purposes. Well, why not outsource the caching bit
to a different class. For example (psuedo-code):

class Date
def to_s
DateCache.to_s(self) {
# Calculate the string rep here. This code
# is only called if necessary to populate the
# cache for the first time.
}
end
end

Then again, you can just store the cache info in a class variable.

Not that I've scrutinised the code or anything.

Gavin
 
P

Paul Brannan

The cleanest might be to introduce something similar to C++'s keyword
'mutable', which allows instance variables to be changed even if the
container is const (aka frozen). But I don't like this approach
because things soon get messy. Constness is difficult to get right
(not so much for the language but rather for users of the language)
IMHO.

There's no need for this. You can't change what object an instance
variable is referring to in a frozen object, but you can modify the
object itself. So to get the effect you are looking for, you can write:

class Foo
Mutables = Struct.new:)foo)

def initialize
@mutables = Mutables.new(42)
end

def mutate(x)
@mutables.foo = x
end
end

f = Foo.new
f.freeze
f.mutate(10)
p f
Alternative solutions could make use of WeakReference somehow,
although this is just a faint idea - nothing concrete.

If I understand you correctly you are talking about solving the problem
of wanting some part of the code to be able to modify an object but some
other part of the code to not be able to do this. I've toyed with this
idea before.

One idea I had is to create a reference that unfreezes the object
before allowing you to use it. In order for this to work, it has to set
Thread.critical=true (otherwise threads that are working with const
references would be able to modify the object). The problem with this
is that the reference has know knowledge about the behavior of the
underlying object (it may make certain assumptions about frozen
instances; see [ruby-talk:8047]).

Another idea I had was to create a reference that only allows the user
to call read-only methods. The reference has to "know" somehow which
methods are safe to allow; it can do this by freezing the object and
trying the operation (same problem as above) or by keeping a list of
safe operations (which can be a maintenance headache, plus there's the
question of what to do when the user calls #to_s on a reference to a
String).

One problem in general with using delegates as references is that you
have something that looks like a real object but is not. If you try to
pass a weakref to a string into a C extension that is expecting a
string, it won't work.

IMO the only sensible implementation would be to change the interpreter
to include two types of references: one that allows changes and another
that does not. It's not a small change, and Matz has already rejected
this idea (see RCR#92 at http://rcrchive.net/rgarchive/rejected.html#rcr92 or
http://www.google.com/search?q=cache:dzj80Q78zgMJ:rubygarden.com/article.php?sid=222+"rcr+92"&hl=en
if you want a threaded view of the discussion).

Lastly, I can definitely see some potential uses for this feature (e.g.
for safe.rb), but for most cases dup and/or freeze and/or the trick
mentioned above will work. I'm not sure if it makes sense to complicate
a library or the interpreter for a feature that is rarely useful (though
the same argument could have been made about callcc).

Paul
 
S

Sean O'Dell

Just an idea regarding frozen objects: this and other myriad situations could
be handled by object "states." That is, when in a certain "state", only
certain methods are available to an object, and those methods can be aliased.
When state changes, the methods swap out. For example, and this is just
pseudocode, mind you (I am making up some syntax that doesn't really exist):

class MyClass

def initialize
state[:eek:n] = false
end

def on_true
return "TRUE"
end

def on_false
return "FALSE"
end

alias :eek:n :eek:n_false

state :eek:n, :state => :eek:n_state

end

m = MyClass.new
p m.on => "FALSE"
m.state[:eek:n] = true
p m.on => "TRUE"

The way a frozen state could be implemented (aside from how it already is,
this is just a from-scratch idea) would be through states. Whenever an
object state changes, it would first restore the last state it was in, so you
could have a class like this:

class MyClass
def initialize
state[:frozen] = false
@var = nil
end

def var
@var
end

def var=(value)
@var = value
end

state :frozen, :var= => nil
end

m = MyClass.new
p m.var => nil
m.var = "SOMEVALUE"
p m.var => "SOMEVALUE"
m.state[:frozen] = true
m.var = "ANOTHERVALUE" => no such method error

Sean O'Dell
 
R

Robert Klemme

Sean O'Dell said:
Just an idea regarding frozen objects: this and other myriad situations could
be handled by object "states." That is, when in a certain "state", only
certain methods are available to an object, and those methods can be aliased.
When state changes, the methods swap out. For example, and this is just
pseudocode, mind you (I am making up some syntax that doesn't really exist):

class MyClass

def initialize
state[:eek:n] = false
end

def on_true
return "TRUE"
end

def on_false
return "FALSE"
end

alias :eek:n :eek:n_false

state :eek:n, :state => :eek:n_state

end

m = MyClass.new
p m.on => "FALSE"
m.state[:eek:n] = true
p m.on => "TRUE"

The way a frozen state could be implemented (aside from how it already is,
this is just a from-scratch idea) would be through states. Whenever an
object state changes, it would first restore the last state it was in, so you
could have a class like this:

class MyClass
def initialize
state[:frozen] = false
@var = nil
end

def var
@var
end

def var=(value)
@var = value
end

state :frozen, :var= => nil
end

m = MyClass.new
p m.var => nil
m.var = "SOMEVALUE"
p m.var => "SOMEVALUE"
m.state[:frozen] = true
m.var = "ANOTHERVALUE" => no such method error

That's basically the State Pattern: behavior of the instance changes
according to state. You can do that with current Ruby alread, if you
refrain from using #freeze and just implement some state pattern handling:

# roughly sketched

module Stateful
def initialize
@states = {}
@state = nil
end

def state; @state; end

def state=(st)
raise "Illegal" unless @states.has_key? st
@state = st
end

def send(sym, *args, &b)
@states[@state].send(sym, *args, &b)
end
end


Regards

robert
 
R

Robert Klemme

Paul Brannan said:
If I understand you correctly you are talking about solving the problem
of wanting some part of the code to be able to modify an object but some
other part of the code to not be able to do this.

I rather thought along the lines of having cached data be referenced by a
WekRef so it can be discarded. But the WeakRef then must be able to
change, which it can't at the moment.

Regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,146
Messages
2,570,832
Members
47,375
Latest member
FelishaCma

Latest Threads

Top