#1787 Serialization shenanigans

SlimerDude Sat 25 Feb 2012

I have a feeling the current Serialization Docs along with `http://fantom.org/sidewalk/topic/1005#c7344` and `http://fantom.org/sidewalk/topic/832` don't tell the whole story...

They all suggest that a non-nullable it block works fine when it comes to serialization, and indeed the following works fine:

@Serializable
class Dude {
  const Int x; const Int y

  new make(|This| f) { f.call(this) }

  static Void main(Str[] args) {
    dude := Dude{x=3; y=4} 
    StrBuf().out.writeObj(dude)
  }
}

However, the addition of skipDefaults when serializing results in:

sys::Err: Type missing "make" or "defVal" slots: afalife::Dude

But making the it block nullable makes everything okay again:

@Serializable
class Dude {
  const Int x; const Int y

  new make(|This|? f := null) { f?.call(this) }

  static Void main(Str[] args) {
    dude := Dude{x=3; y=4} 
    StrBuf().out.writeObj(dude, ["skipDefaults" : true])
  }
}

Could the example in the Serialization Docs be updated to show a nullable it block, for I feel this is a trap for young players!

Cheers.

SlimerDude Sat 25 Feb 2012

( I had a feeling this was going to be a multi-part post! )

Updating the example above to use a Simple Serializable type is fine:

@Serializable{simple=true}
const class Pie {
  const Int wot
  new make(Int wot) { this.wot = wot }
  override Str toStr() { "$wot" }
  static Pie fromStr(Str str) { Pie(str.toInt) }
}

@Serializable
const class Sausage {
  const Pie x; const Pie y

  new make(|This|? f := null) { f?.call(this) }

  static Void main(Str[] args) {
    sausage := Sausage{x=Pie(3); y=Pie(4)} 
    StrBuf().out.writeObj(sausage)
  }
}

But again, serializing with skipDefaults gives:

StrBuf().out.writeObj(sausage, ["skipDefaults":true])

sys::FieldNotSetErr: afalife::Sausage.x

Only this time I'm not sure what's on or how to overcome it (well, besides from not using skipDefaults!

SlimerDude Sat 25 Feb 2012

While I'm on a roll of Fantom language abuse, here's another serialization example I thought would work but doesn't:

@Serializable
const class Flying {
  new make(Str name) { echo(name) }
}

@Serializable
const class Sausage : Flying {
  const Int w := 66

  new make(|This|? f := null, Str? name := "Cow") : super(name) {
    f?.call(this) 
  }

  static Void main(Str[] args) {
    sausage := Sausage()
    buff := StrBuf()
    buff.out.writeObj(sausage)
    echo( buff.toStr )  
    Sausage s2 := buff.toStr.in.readObj
  }
}

gives

sys::IOErr: Cannot make afalife::Sausage: sys::ReadonlyErr: Cannot set const field afalife::Sausage.w [Line 1]
  fanx.serial.ObjDecoder.err (ObjDecoder.java:665)
  fanx.serial.ObjDecoder.readComplex (ObjDecoder.java:249)
  fanx.serial.ObjDecoder.readObj (ObjDecoder.java:146)
  fanx.serial.ObjDecoder.readObj (ObjDecoder.java:55)
  fan.sys.InStream.readObj (InStream.java:560)
  fan.sys.InStream.readObj (InStream.java:557)
  afalife::Sausage.main (SerialTest.fan:61)
Cause:
  sys::ReadonlyErr: Cannot set const field afalife::Sausage.w
    fan.sys.Field.set (Field.java:115)
    fan.sys.Field$1.call (Field.java:37)
    afalife::Sausage.make$ (SerialTest.fan:53)
    afalife::Sausage.make$ (SerialTest.fan)
    afalife::Sausage.make (SerialTest.fan:52)
    java.lang.reflect.Method.invoke (Unknown)
    fan.sys.Method.invoke (Method.java:558)
    fan.sys.Method$MethodFunc.callList (Method.java:198)
    fan.sys.Method.callList (Method.java:138)
    fanx.serial.ObjDecoder.readComplex (ObjDecoder.java:245)
    fanx.serial.ObjDecoder.readObj (ObjDecoder.java:146)
    fanx.serial.ObjDecoder.readObj (ObjDecoder.java:55)
    fan.sys.InStream.readObj (InStream.java:560)
    fan.sys.InStream.readObj (InStream.java:557)
    afalife::Sausage.main (SerialTest.fan:61)
    3 More...

The solution is to move the it block to the end of the parameter list:

new make(Str name := "Cow", |This|? f := null) : super(name) { ... }

On digging through this forum, it's mentioned briefly in passing in `http://fantom.org/sidewalk/topic/832#c7551`

(By the way, these aren't random examples I've been trying out, I'm narrowing down the reasons why I can't pass messages between Actors in an AI app I'm porting over.)

Obviously Fantom serialization is rather powerful and ingrained in the language from the core... and I can't help thinking that more meaningful Err messages and a solid list of what you can and can't do in the documentation, would go a long way in helping us untrained Newbies!

KevinKelley Sat 25 Feb 2012

Looking in src/sys/java/fanx/ObjEncoder.java I see there's a direct call to FanObj.typeof(obj).make() when skipDefaults is true... I think the recent changes to Fantom constructors may cause that to fail in some situations...

brian Sat 25 Feb 2012

Promoted to ticket #1787 and assigned to brian

What we have here is a bad interaction between two of Fantom's most complex features: serialization and const classes. I'll need to some spend some time looking at your use cases and maybe tightening up the serialization semantics for const classes.

Regarding your note that you are doing this for Actors - if you have const classes, you don't need serialization. You just pass the messages by reference. In fact I don't think I've ever used serialization based messages, and I'm wondering if that was a mistake and we should force all messages to be const (it would make serialization to Str a manual process).

KevinKelley Sat 25 Feb 2012

force all messages to be const

I hope not; I find a lot of times const is too onerous, but serializable gives me an out. For instance any structure that's not a DAG -- if you need both forward and back links, it's either impossible, or too hard, to make the elements be const. But you can serialize by fixing up the links yourself...

brian Sat 25 Feb 2012

Okay good to know what some people use that mechanism

SlimerDude Sat 25 Feb 2012

Sweet, I'm well pleased I've found some nefarious middle ground and I'm not just being a pain!

Regarding your note that you are doing this for Actors - if you have const classes, you don't need serialization.

This I don't quite understand. When I tried passing my classes between Actors I got Errs, from the docs I assumed they'd be serialization Errs and wrote the test cases; where I got (similar?) Errs. After fixing up the serialization test cases, my main code then started working! (For F1.0.60 that is, I make heavy use of Ranges!)

brian Sat 25 Feb 2012

This I don't quite understand. When I tried passing my classes between Actors I got Errs

If your class is const it is just passed by reference - that is the optimal way to do messaging because its really efficient and fast. The check is actually Obj.isImmutable (since List, Maps, and Funcs have special rules).

brian Wed 18 Oct 2017

Ticket cancelled

Re-reading this, I think it was a duplicate of 2644

SlimerDude Wed 18 Oct 2017

Agreed, most of it is a duplicate of `#2644`, but a quick note on the problem in the 3rd comment...

The comment presents this code:

@Serializable
const class Flying { }

@Serializable
const class Sausage : Flying {
  const Int oops := 66

  new make(|This|? f := null, Str? name := null) : super.make() {
    f?.call(this)
  }
}

class Example {
  Void main() {
    Buf().writeObj( Sausage() ).flip.readObj
  }
}

And gives this error when run:

sys::ReadonlyErr: Cannot set const field Ser_0::Sausage.oops

The issue is the ctor of Sausage which takes an it-block arg as the first parameter. If you switch the parameters around, so the it-block is last, then everything works fine:

new make(Str? name := null, |This|? f := null) : super.make() { ... }

While experienced Fantom developers will naturally place the it-block at the end of the parameter list as per `#832`, my concern is that this is not obvious to less experienced Fantom developers. (Well, it wasn't obvious to me when I wrote this topic!)

So I have 2 questions really:

  1. Is there a valid reason to not put an it-block parameter at the end of the parameter list?
  2. If not, then could the compiler spit out a warning (or error?) if an it-block parameter is not at the end of a ctor param list?

brian Fri 20 Oct 2017

It was not the intention that serialization worked with anything other than a constructor that took a single it-block. The fact that any of those work is a side effect of perhaps not enough error checking

Login or Signup to reply.