Normally Fantom doesn't let you create a const class containing a non-cost type. However values assigned to generic types are automatically converted to immutable values.
This works well in practice but the introspection is imprecise. If both const and non-cost objects are created via introspection it's hard to tell if a value assigned to a field should or shouldn't be converted to immutable. As the example above demonstrates that checking field.type doesn't help.
What is needed in the current implementation is to check objects all the way to up the highest parent to see if there is a const modifier or not and then convert Lists and Maps accordingly.
I think life could be much simplier if Fantom fixed flags like this in runtime. So fields for const classes would all report isConst=true.
Creating const classes is already tricky enough. You need to figure out that passing Funcs to constructor parameters is not enough for the compiler and that you need to use makeSetFunc to make it happy even though a non-programmatic case works just fine.
If you wonder how did I get there - I needed to serialize/deserialize some const objects along with non-const ones ;)
EDIT: fixed a missing line in the example
yliuTue 4 Sep 2012
In your example above, you're checking if the stored type of lst which is List# is const, which will not be true of course. However if you modify your example to be instead:
the lst field will come up as true, which makes sense because ConstObj's lst field is declared as const.
Maybe I am missing the point, but at least in your example I don't see how you can relate the stored type's isConst to the ConstObj's field's isConst. (ie. List#.isConst shouldn't be comparable to ConstObj.field("lst").isConst)
tcolarTue 4 Sep 2012
I think you are abusing the string interpolation mechanism to start with.
katoxTue 4 Sep 2012
@yliu you are right, I should be checking if the slot is const not its type.
However you can't define a const class where a const field has a non-const type for any but sys types. If you try that you'll see the compiler complaining "Const field xxx has non-const type".
That compiler message suggests that you can in a way relate const fields and their types. Based on the wording of the error it should be really equal checking either slot or its type for constantness.
But it is obviously not. Why is the limitation there? Shouldn't it suffice to require only immutable values to be assigned? const modifier should prevent reassigning anyway.
tcolarTue 4 Sep 2012
More seriously I remember the being an issue with constructors and it blocks and the fact you could reassign variables in the constructor ... and that's probably a big part of this.
brianTue 4 Sep 2012
Just to clear things up, it is important to note that const types are not exactly the same as immutable instances. Consider this example:
In the example the type of the list is Obj?[] - that is not a const type because technically we don't know what is might store. However the list instance is immutable because we have checked every item and verified that it is indeed immutable.
When I'm trying to be precise I use the term const for types, but immutable for instances.
A const field doesn't necessarily require a const field type, but it does require an immutable instance.
katoxWed 5 Sep 2012
A const field doesn't necessarily require a const field type, but it does require an immutable instance.
Then why does the compiler complain on this?
const class ConstObj
{
const NonConstClass obj := NonConstClass("hello")
new make (|This| f) { f(this) }
}
class NonConstClass
{
const Str string
new make (Str s) { string = s }
}
// Test.fan(99,5): Const field 'obj' has non-const type 'mytest::NonConstClass'
// BUILD FAILED
brianWed 5 Sep 2012
It is the same philosophy that permeates through the entire Fantom language. If we know something to be invalid at compile time, then it is a compile time error. If it might work at runtime, then we delay the check until runtime and allow the code to compile.
In your case NonConstClass is known to never be immutable because it is a non-const class. If a const class we know that is always immutable. And then there are a few special types such as Obj that we don't know until runtime.
So in your case the type field is NonConstClass and that is a compile time error. If you changed your field type to Obj and then assigned an instance of NonConstClass in the constructor that would be a runtime error.
However you can't define a const class where a const field has a non-const type for any but sys types. If you try that you'll see the compiler complaining "Const field xxx has non-const type".
I think this should be the correct behavior. sys::List and sys::Map should be the only non-const fields allowed due to their container nature. Their "const" is defined by what they contain and not by the class itself.
Also this is satisfied by the fact that when you assign sys::List and sys::Map fields in the constructor, .toImmutable is called on each item to make sure every item is const (even if they contain other instances of sys::List and sys::Map) and throw an error if there are any non-const objects.
ie. This would throw an error
const class ConstObj
{
const Str string
const Obj?[] lst;
new make(|This| f) {f(this)}
static Void main()
{
c := ConstObj(){ it.string="hello"; it.lst = [Bacon()].toImmutable}
echo("ConstObj#.isConst=${ConstObj#.isConst}")
ConstObj#.fields.each |f| { echo("${f.type}.isConst=${f.isConst}") }
}
}
class Bacon
{
new make()
{
}
}
What are you trying to accomplish though? Maybe there's another way! :D
katoxThu 6 Sep 2012
I think this should be the correct behavior. sys::List and sys::Map should be the only non-const fields allowed due to their container nature. Their "const" is defined by what they contain and not by the class itself.
This is the same as with NonConstClass above. All its instances are effectivelly immutable because it's just a Str wrapper with no other state. There is no way to really mutate the class since string field can't be reassigned. The only difference is that it can't be marked as immutable to the compiler while sys types (like List, Map and Func) can.
But I understand that the compiler must guarantee that const classes contain only immutable values that can't change once constructed. While the example above would actually work (if runtime didn't check the immutable marker flag) the compiler needs to be sure that there are only provably immutable values so it restricts such usage.
So I guess if there was a const variant for every type a const class would only contain const fields with const types. But there are not so the runtime allows to put there some non-const types and checks the immutability flag later.
What are you trying to accomplish though? Maybe there's another way! :D
I've already accomplished what I needed to do (deserializing a mixture of const and non-const classes and types) so this thread is more or less a mental exercise. But I simplified some code based on your note that I should check field not field type properties. Thanks for that ;)
And thanks to Brian for the doc links. It might be handy to add the concurrency link to sections mentioning const in classes and fields.
katox Tue 4 Sep 2012
This doesn't seem quite right:
Normally Fantom doesn't let you create a const class containing a non-cost type. However values assigned to generic types are automatically converted to immutable values.
This works well in practice but the introspection is imprecise. If both const and non-cost objects are created via introspection it's hard to tell if a value assigned to a field should or shouldn't be converted to immutable. As the example above demonstrates that checking field.type doesn't help.
What is needed in the current implementation is to check objects all the way to up the highest parent to see if there is a const modifier or not and then convert Lists and Maps accordingly.
I think life could be much simplier if Fantom fixed flags like this in runtime. So fields for const classes would all report isConst=true.
Creating const classes is already tricky enough. You need to figure out that passing Funcs to constructor parameters is not enough for the compiler and that you need to use makeSetFunc to make it happy even though a non-programmatic case works just fine.
If you wonder how did I get there - I needed to serialize/deserialize some const objects along with non-const ones ;)
EDIT: fixed a missing line in the example
yliu Tue 4 Sep 2012
In your example above, you're checking if the stored type of lst which is List# is const, which will not be true of course. However if you modify your example to be instead:
the lst field will come up as true, which makes sense because ConstObj's lst field is declared as const.
Maybe I am missing the point, but at least in your example I don't see how you can relate the stored type's isConst to the ConstObj's field's isConst. (ie. List#.isConst shouldn't be comparable to ConstObj.field("lst").isConst)
tcolar Tue 4 Sep 2012
I think you are abusing the string interpolation mechanism to start with.
katox Tue 4 Sep 2012
@yliu you are right, I should be checking if the slot is const not its type.
However you can't define a const class where a const field has a non-const type for any but sys types. If you try that you'll see the compiler complaining "Const field
xxx
has non-const type".That compiler message suggests that you can in a way relate const fields and their types. Based on the wording of the error it should be really equal checking either slot or its type for constantness.
But it is obviously not. Why is the limitation there? Shouldn't it suffice to require only immutable values to be assigned? const modifier should prevent reassigning anyway.
tcolar Tue 4 Sep 2012
More seriously I remember the being an issue with constructors and it blocks and the fact you could reassign variables in the constructor ... and that's probably a big part of this.
brian Tue 4 Sep 2012
Just to clear things up, it is important to note that const types are not exactly the same as immutable instances. Consider this example:
In the example the type of the list is
Obj?[]
- that is not a const type because technically we don't know what is might store. However thelist
instance is immutable because we have checked every item and verified that it is indeed immutable.When I'm trying to be precise I use the term const for types, but immutable for instances.
A const field doesn't necessarily require a const field type, but it does require an immutable instance.
katox Wed 5 Sep 2012
A const field doesn't necessarily require a const field type, but it does require an immutable instance.
Then why does the compiler complain on this?
brian Wed 5 Sep 2012
It is the same philosophy that permeates through the entire Fantom language. If we know something to be invalid at compile time, then it is a compile time error. If it might work at runtime, then we delay the check until runtime and allow the code to compile.
In your case NonConstClass is known to never be immutable because it is a non-const class. If a const class we know that is always immutable. And then there are a few special types such as
Obj
that we don't know until runtime.So in your case the type field is NonConstClass and that is a compile time error. If you changed your field type to Obj and then assigned an instance of NonConstClass in the constructor that would be a runtime error.
You can see Concurrency chapter for full details on the topic.
yliu Wed 5 Sep 2012
However you can't define a const class where a const field has a non-const type for any but sys types. If you try that you'll see the compiler complaining "Const field xxx has non-const type".
I think this should be the correct behavior. sys::List and sys::Map should be the only non-const fields allowed due to their container nature. Their "const" is defined by what they contain and not by the class itself.
Also this is satisfied by the fact that when you assign sys::List and sys::Map fields in the constructor, .toImmutable is called on each item to make sure every item is const (even if they contain other instances of sys::List and sys::Map) and throw an error if there are any non-const objects.
ie. This would throw an error
What are you trying to accomplish though? Maybe there's another way! :D
katox Thu 6 Sep 2012
I think this should be the correct behavior. sys::List and sys::Map should be the only non-const fields allowed due to their container nature. Their "const" is defined by what they contain and not by the class itself.
This is the same as with NonConstClass above. All its instances are effectivelly immutable because it's just a Str wrapper with no other state. There is no way to really mutate the class since
string
field can't be reassigned. The only difference is that it can't be marked as immutable to the compiler while sys types (like List, Map and Func) can.But I understand that the compiler must guarantee that const classes contain only immutable values that can't change once constructed. While the example above would actually work (if runtime didn't check the immutable marker flag) the compiler needs to be sure that there are only provably immutable values so it restricts such usage.
So I guess if there was a const variant for every type a const class would only contain const fields with const types. But there are not so the runtime allows to put there some non-const types and checks the immutability flag later.
What are you trying to accomplish though? Maybe there's another way! :D
I've already accomplished what I needed to do (deserializing a mixture of const and non-const classes and types) so this thread is more or less a mental exercise. But I simplified some code based on your note that I should check field not field type properties. Thanks for that ;)
And thanks to Brian for the doc links. It might be handy to add the concurrency link to sections mentioning const in classes and fields.