I don't find any good reason for once methods not to be allowed in immutable classes. I think the language would be much more powerful if they were.
Lazy evaluation does not violate immutability. Sure, lazy evaluation is implemented with a field that has a state (to remember if the value was initialized), but this is not visible from outside the class.
Another example - if you look at the Google implementation of ImmutableList, it has a "asSet()" method with returns a set-view of the list. This set is computed lazily.
In Fantom, it would not be possible to do this. Either the "set-view" would be computed in the constructor, and this would generate a performance loss in most cases because "asSet" is rarely called. Either the "set-view" would be computed every time "asSet" is called, which would generate a performance loss if the client calls this method many times.
One last note - if implemented correctly, lazy implementation is thread-safe. In Java, this requires the use of the volatile keyword, and this pretty ugly piece of code:
X getX() {
X result = cached;
if (result == null) {
synchronized (this) {
result = cached;
if (result == null) {
cached = result = expensiveComputation();
}
}
}
return result;
}
This is a little frustrating because Fantom is the first language (to my knowledge) that tackles lazy evaluation. But yet, Fantom forbids to use it in the case where it would be the most useful, that is immutable classes.
tcolarWed 16 Jan 2013
I think that could get a little weird with serialization for example. For example what if you serialized before and then after calling the once ?
I suppose maybe a once method could require / imply Transient then it might be ok.
But the result of a once method needs to be cached internally, after construction, and there is no good/safe way to store it then in a const class, maybe internally Fantom could cache that in actor.locals or something, but then that could also cause more weirdness, what if it gets passed to another thread, value would have to be recalculated ??
So I think there are good reason why you can't have those in a const class.
brianWed 16 Jan 2013
I agree it might be nice to support on immutable classes. But I also think that such a feature opens a huge can of worms. It is only thread safe if we assume that that computation of the once method isn't going to do anything like call other immutable once methods or make actor calls etc. Otherwise we are opening ourselves up to race conditions. And we'd have to decide what the semantics should really be - is it something guaranteed to only be called once in which case a synchronized block is required (and introduces potential for deadlocks), or is it merely a cache where even if computed by multiple threads, last one wins is ok (just need volatile field in that case). Be curious to hear what other people think...
clementrWed 16 Jan 2013
Thanks a lot for your answer.
For serialization, I think that considering all once fields as transient would be okay and make a lot of sense.
About the second point you raise, I don't know what actor.locals is so I guess I am not familiar enough with how Fantom is implemented to understand. I am putting myself from a user's perspective. In Java, there is a way to make sure lazy evaluation is thread-safe, AND that it never gets recalculated, even if 2 threads call it in the same time. It is detailed in Josh Bloch's Effective Java (item: lazy evaluation).
X getX() {
X result = cached;
if (result == null) {
synchronized (this) {
result = cached;
if (result == null) {
cached = result = expensiveComputation();
}
}
}
return result;
}
If it's not for immutable classes, what is the point of having once? Having once methods on classes that are mutable seem very dangerous, since the method will forever return a value that was computed when the class had a particular state, which may have changed since then.
clementrWed 16 Jan 2013
Hi Brian,
I had not seen your response when I was answering to tcolar.
I totally get the point you raise, about the difficulty of having to decide between: 1) giving the guarantee that the method will be called once (and thus using synchronization) 2) or stating that the method may accidentally be called several times.
The only goal of lazy evaluation on an immutable object is performance. Avoid having to recompute results. But also, avoid having to recreate new objects, as creating a new object that will be garbage-collected has a cost.
My opinion on the issue you raise is that (2) is fine. The only definition of the once keyword could be:
Use this keyword on no-argument methods that perform expensive computation and that may get called several times. The compiler may cache the result for performance.
Nothing else really needs to be specified. This definition is simple for the user to understand, and also would give the possibility to do smarter things later, e.g. decide to switch to (1) if some conditions are met. Or even not do lazy evaluation at all, if the compiler judges that the computation is very simple.
clementrWed 16 Jan 2013
One last point Brian, you wrote:
It is only thread safe if we assume that that computation of the once method isn't going to do anything like call other immutable once methods or make actor calls etc.
Why would we loose thread-safety when calling another immutable once method?
clementr Wed 16 Jan 2013
Hello all,
I don't find any good reason for
once
methods not to be allowed in immutable classes. I think the language would be much more powerful if they were.Lazy evaluation does not violate immutability. Sure, lazy evaluation is implemented with a field that has a state (to remember if the value was initialized), but this is not visible from outside the class.
In Java, I can think of plenty of immutable classes that use lazy initialization (without the client having to know it). For example, the hash code of a String is computed lazily: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java#1493
Another example - if you look at the Google implementation of ImmutableList, it has a "asSet()" method with returns a set-view of the list. This set is computed lazily.
In Fantom, it would not be possible to do this. Either the "set-view" would be computed in the constructor, and this would generate a performance loss in most cases because "asSet" is rarely called. Either the "set-view" would be computed every time "asSet" is called, which would generate a performance loss if the client calls this method many times.
One last note - if implemented correctly, lazy implementation is thread-safe. In Java, this requires the use of the volatile keyword, and this pretty ugly piece of code:
This is a little frustrating because Fantom is the first language (to my knowledge) that tackles lazy evaluation. But yet, Fantom forbids to use it in the case where it would be the most useful, that is immutable classes.
tcolar Wed 16 Jan 2013
I think that could get a little weird with serialization for example. For example what if you serialized before and then after calling the once ?
I suppose maybe a once method could require / imply Transient then it might be ok.
But the result of a once method needs to be cached internally, after construction, and there is no good/safe way to store it then in a const class, maybe internally Fantom could cache that in actor.locals or something, but then that could also cause more weirdness, what if it gets passed to another thread, value would have to be recalculated ??
So I think there are good reason why you can't have those in a const class.
brian Wed 16 Jan 2013
I agree it might be nice to support on immutable classes. But I also think that such a feature opens a huge can of worms. It is only thread safe if we assume that that computation of the once method isn't going to do anything like call other immutable once methods or make actor calls etc. Otherwise we are opening ourselves up to race conditions. And we'd have to decide what the semantics should really be - is it something guaranteed to only be called once in which case a synchronized block is required (and introduces potential for deadlocks), or is it merely a cache where even if computed by multiple threads, last one wins is ok (just need volatile field in that case). Be curious to hear what other people think...
clementr Wed 16 Jan 2013
Thanks a lot for your answer.
For serialization, I think that considering all
once
fields as transient would be okay and make a lot of sense.About the second point you raise, I don't know what
actor.locals
is so I guess I am not familiar enough with how Fantom is implemented to understand. I am putting myself from a user's perspective. In Java, there is a way to make sure lazy evaluation is thread-safe, AND that it never gets recalculated, even if 2 threads call it in the same time. It is detailed in Josh Bloch's Effective Java (item: lazy evaluation).Some people don't mind if the result gets accidentally recomputed if 2 threads call the method in the same time, so they omit the synchronized. See for example the implementation of
ImmutableCollection.asList
in Google collections: https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/collect/ImmutableCollection.java?r=5dc1f62c8d234c8eb45bac7d9c4732eca59f67a5#155If it's not for immutable classes, what is the point of having
once
? Havingonce
methods on classes that are mutable seem very dangerous, since the method will forever return a value that was computed when the class had a particular state, which may have changed since then.clementr Wed 16 Jan 2013
Hi Brian,
I had not seen your response when I was answering to tcolar.
I totally get the point you raise, about the difficulty of having to decide between: 1) giving the guarantee that the method will be called once (and thus using synchronization) 2) or stating that the method may accidentally be called several times.
The only goal of lazy evaluation on an immutable object is performance. Avoid having to recompute results. But also, avoid having to recreate new objects, as creating a new object that will be garbage-collected has a cost.
My opinion on the issue you raise is that (2) is fine. The only definition of the
once
keyword could be:Nothing else really needs to be specified. This definition is simple for the user to understand, and also would give the possibility to do smarter things later, e.g. decide to switch to (1) if some conditions are met. Or even not do lazy evaluation at all, if the compiler judges that the computation is very simple.
clementr Wed 16 Jan 2013
One last point Brian, you wrote:
Why would we loose thread-safety when calling another immutable once method?