How to implement custom serialization/deserialization for a struct in Julia? - serialization

The default implementation of Base.serialize and Base.deserialize do the serialization/deserialization for the whole given object.
What is the correct way of excluding a field from being serialized and still be able to deserialize it correctly?
Here is a simplified code sample:
# The target struct
struct Foo
x::Int
y::Union{Int, Void} #we do not want to serialize this field
end
foo1 = Foo(1,2)
# Serialization
write_iob = IOBuffer()
serialize(write_iob, foo1)
seekstart(write_iob)
content = read(write_iob)
# Deserialization
read_iob = IOBuffer(content)
foo2 = deserialize(read_iob)
#show foo1
#show foo2
The output of the above code is:
foo1 = Foo(1, 2)
foo2 = Foo(1, 2)
And the desired result should be:
foo1 = Foo(1, 2)
foo2 = Foo(1, nothing)
Here, I assume that we can define a default value for the missing fields, e.g., nothing for yin the above output.

After digging into the implementation of serialization/deserialization in my current version of Julia (0.6.2), I found a solution. Here is the solution that worked for the example in the question:
# Custom Serialization of a Foo instance
function Base.Serializer.serialize(s::AbstractSerializer, instance::Foo)
Base.Serializer.writetag(s.io, Base.Serializer.OBJECT_TAG)
Base.Serializer.serialize(s, Foo)
Base.Serializer.serialize(s, instance.x)
end
# Custom Deserialization of a Foo instance
function Base.Serializer.deserialize(s::AbstractSerializer, ::Type{Foo})
x = Base.Serializer.deserialize(s)
Foo(x,nothing)
end
Now, if you run the test code again:
# The target struct
struct Foo
x::Int
y::Union{Int, Void} #we do not want to serialize this field
end
foo1 = Foo(1,2)
# Serialization
write_iob = IOBuffer()
serialize(write_iob, foo1)
seekstart(write_iob)
content = read(write_iob)
# Deserialization
read_iob = IOBuffer(content)
foo2 = deserialize(read_iob)
#show foo1
#show foo2
The test code outputs:
foo1 = Foo(1, 2)
foo2 = Foo(1, nothing)
I should mention that the above solution depends on the current implementation of serialization/deserialization (in Julia 0.6.2) and there's no guarantee about its stability in the future. Hence, I'll still keep an eye on finding a better solution.
Update: The above code does not work after Julia 1.0. Here is the updated code:
using Serialization
# The target struct
struct Foo
x::Int
y::Union{Int, Nothing} #we do not want to serialize this field
end
# Custom Serialization of a Foo instance
function Serialization.serialize(s::AbstractSerializer, instance::Foo)
Serialization.writetag(s.io, Serialization.OBJECT_TAG)
Serialization.serialize(s, Foo)
Serialization.serialize(s, instance.x)
end
# Custom Deserialization of a Foo instance
function Serialization.deserialize(s::AbstractSerializer, ::Type{Foo})
x = Serialization.deserialize(s)
Foo(x,nothing)
end
foo1 = Foo(1,2)
# Serialization
write_iob = IOBuffer()
serialize(write_iob, foo1)
seekstart(write_iob)
content = read(write_iob)
# Deserialization
read_iob = IOBuffer(content)
foo2 = deserialize(read_iob)
#show foo1
#show foo2

Related

"Invocant of method 'ASSIGN-KEY' must be an object instance" when using assignment operator

Hash with typed keys…
use v6;
class Foo {}
my Hash[Foo, Foo] $MAP;
my $f1 = Foo.new;
my $f2 = Foo.new;
$MAP{$f1} = $f2;
produces the error:
Invocant of method 'ASSIGN-KEY' must be an object instance of type 'Hash[Foo,Foo]', not a type object of type 'Hash[Foo,Foo]'. Did you forget a '.new'?
I find it misleading; what's the real error and what do I have to write instead?
I already tried the % sigil for the hash variable, that doesn't work, either.
In the way you have defined it, $MAP is actually a role. You need to instantiate (actually, pun) it:
class Foo {}
my Hash[Foo, Foo] $MAP;
my $map = $MAP.new;
my $f1 = Foo.new;
my $f2 = Foo.new;
$map{$f1} = $f2;
say $map;
Dead giveaway here was that classes can't be parametrized, roles do.
Also:
say $MAP.DEFINITE; # False
say $map.DEFINITE; # True
But actually the error message was pretty informative, up to and including the suggestion to use .new, as I do here.
We can shorten it down to:
class Foo {}
my %map = Hash[Foo, Foo].new ;
%map{Foo.new} = Foo.new;
%map.say;
By doing the punning from the definition, we don't need the $MAP intermediate class.
TL;DR JJ's answer is right, but the explanation left me confused. I currently view the problem you showed as an autovivification error/bug and/or LTA error message.
say my Any $Any; # (Any)
say my Hash $Hash; # (Hash)
say my Hash[Int] $Hash-Int; # (Hash[Int])
$Any<a> = 42; # OK
$Hash<a> = 42; # OK
$Hash-Int.new<a> = 42; # OK
$Hash-Int<a> = 42; # must be an object instance, not a type object
Imo this is a bug or pretty close to one.
A bug/problem applies for arrays too in the same scenario:
say my Any $Any; # (Any)
say my Array $Array; # (Array)
say my Array[Int] $Array-Int; # (Array[Int])
$Any[42] = 42; # OK
$Array[42] = 42; # OK
$Array-Int.new[42] = 42; # OK
$Array-Int[42] = 42; # Type check failed ... expected Array[Int] but got Array
If it's best considered notabug, then perhaps the error message should be changed. While I agree with JJ that the error message is actually on point (when you understand how raku works and figure out what's going on), I think it's nevertheless an LTA error message if we don't change raku(do) to dwim.
On the gripping hand, it's not obvious to me how one could best improve the error message. And now we have this SO. (cf my point about that in Is the ... error message LTA? in a recent answer I wrote.)
Another solution
I already tried the % sigil for the hash variable, that doesn't work, either.
JJ has provided a solution that initializes with a value with an explicit .new. But that drops the constraint from the variable. To retain it:
class Foo {}
constant FooFoo = Hash[Foo:D,Foo:D];
my %foo is FooFoo;
%foo{Foo.new} = Foo.new;
Ideally the constant wouldn't be needed, and perhaps one day it won't, but I think trait parsing is limited.

Idiomatic way to assign values from an array to individual variables

There is an array of length 4
var foo: String? // These variable declarations are out of your control
var foo2: String? // they are part of a bean and an api contract
var foo3: String?
var foo4: String?
var bars: Array<String> = Array(NUMBER_OF_BARS) { "" }
foo = bars[0]
foo2 = bars[1]
foo3 = bars[2]
foo4 = bars[3] // <- Invalid
Any numbers except 0, 1 and 2 in the code result in a
MagicNumber complaint by static code checker and is not allowed.
Basically this question but for kotlin, Java - quick way of assigning array values to individual variables
How is that written idiomatically given the restraints? I tried naming the indexes as the static code checker hints I should do, but it's not that nice to be honest:
const val BAR_1 = 0
const val BAR_2 = 1
const val BAR_3 = 2
const val BAR_4 = 3
That's no fun
If your array is bars then Kotlin allows you to do
var (foo, foo2, foo3, foo4, foo5) = bars
Make sure you aren't trying to create more variables than the array contains though. That will cause an exception. If you aren't creating new variables, then this approach won't work. But the good news is, the above statement is basically just syntactic sugar for using the componentN methods, so you can just use them directly.
foo = bars.component1()
foo2 = bars.component2()
foo3 = bars.component3()
foo4 = bars.component4()
A bit more verbose than just using indices, but it will get your static code checker to shut up.

Generating a method for each subtype within a module

(Reposted from Julia slack for posterity)
let’s say I have some constants like
const FooConst = 1
const BarConst = 2
and I also have some struct
struct Foo end
struct Bar end
and I now want to define a method for each struct to look up that constant
f(::Type{Foo}) = FooConst
f(::Type{Bar}) = BarConst
how would I achieve that last block using metaprogramming? I'm essentially trying to tack on Const to the end of the name of the Struct and look that up in the code
...
...(this) works outside of a module, but in my module the constants aren't exported. After I import my module, f isn't able to look the constants up. MWE here:
module M
import InteractiveUtils: subtypes
export Foo, Bar, f
abstract type Super end
struct Foo <: Super end
struct Bar <: Super end
const FooConst = 1
const BarConst = 2
for T in subtypes(Super)
#eval f(::Type{$T}) = $(Symbol(T, "Const"))
end
end # module
and then in my REPL:
julia> using Main.M
julia> f(Foo)
ERROR: UndefVarError: Main.M.FooConst not defined
Stacktrace:
[1] f(::Type{Foo}) at ./none:11
[2] top-level scope at none:0
I am however able to access it directly:
julia> Main.M.FooConst
1
From Mason Protter on Julia slack:
Mason Protter 2:26 PM
#Sebastian Rollen The issue was the Symbol(T, "const"). That actually ended up expanding to Symbol("Main.Foo.FooConst") and Symbol("Main.Foo.BarConst") instead of Symbol("FooConst") and Symbol("BarConst") respectively. You can fix that using Symbol(nameof(T), "Const") as shown here:
module M
import InteractiveUtils: subtypes
export Foo, Bar, f
abstract type Super end
struct Foo <: Super end
struct Bar <: Super end
const FooConst = 1
const BarConst = 2
for T in subtypes(Super)
#eval f(::Type{$T}) = $(Symbol(nameof(T), "Const"))
end
end # module
julia> using .M; f(Foo)
1
julia> f(Bar)
2
Make sure you restart Julia before running this code or Julia will continue to use the exported functions from the old version of your module.

Abstract types in modules in OCaml

I have very simple signature and module in OCaml:
module type S = sig
type t
val y : t
end;;
and
module M2 : S = struct
type t = int
let x = 1
let y = x+2
end;;
I cannot use construction like
M2.y
to get 3 unless i specify the module as
module M2 : S with type t = int = struct ...
Why is it so? There already is statement, that type t = int
The concrete, int value for M2.y is indeed not available because the following two conditions are met:
the type of y is abstract in the signature S
(there is no type t = ... there)
the module M2 is made opaque with respect to the signature S
(in other words, it is restricted to the signature S via the notation : S)
As a result, you indeed obtain:
let test = M2.y ;;
(* val test : M2.t = <abstr> *)
As suggested by the keyword <abstr>, this is related to the notion of abstract type. This notion is a very strong feature enforced by OCaml's typing rules, which prevents any user of a module having signature S to inspect the concrete content of one such abstract type. As a result, this property is very useful to implement so-called abstract data types (ADT) in OCaml, by carefully separating the implementation and the signature of the ADT.
If any of the two conditions above is missing, the type won't be abstract anymore and the concrete value of y will show up.
More precisely:
If the type t is made concrete, you obtain:
module type S = sig
type t = int
val y : t
end
module M2 : S = struct
type t = int
let x = 1
let y = x+2
end
let test = M2.y ;;
(* val test : M2.t = 3 *)
But in practice this is not very interesting because you lose generality. However, a somewhat more interesting approach consists in adding an "evaluator" or a "pretty-printer" function to the signature, such as the value int_of_t below:
module type S = sig
type t
val y : t
val int_of_t : t -> int
end
module M2 : S = struct
type t = int
let x = 1
let y = x+2
let int_of_t x = x
end
let test = M2.(int_of_t y) ;;
(* val test : int = 3 *)
Otherwise, if the module M2 is made transparent, you obtain:
module type S = sig
type t
val y : t
end
module M2 (* :S *) = struct
type t = int
let x = 1
let y = x+2
end
let test = M2.y ;;
(* val test : int = 3 *)
Finally, it may be helpful to note that beyond that feature of abstract types, OCaml also provides a feature of private types that can be viewed as a trade-off between concrete and abstract types used in a modular development. For more details on this notion, see for example Chap. 8 of Caml ref man.

Relaxing type checking when using 'with type' construction in modules

I have defined two module types and two modules
module type FOO = sig type e end
module type BAR = sig type t end
module Foo : FOO = struct type e = int end
module Bar : BAR = struct type t = int end
Then I define a functor as
module Fun (F:FOO) (B:BAR with type t = F.e) = struct type x = string end
(this is a toy example, please ignore the fact that F and B are not used by the functor)
Now, if I define the module
module Bla = Fun (Foo) (Bar)
I get
Error: Signature mismatch:
Modules do not match:
sig type t = Bar.t end
is not included in
sig type t = Foo.e end
Type declarations do not match:
type t = Bar.t
is not included in
type t = Foo.e
Although both Bar.t and Foo.e are defined as int OCaml considers Bar.t and Foo.e to be different. That's just the way the typing system works and it makes sense to consider these two types different in general (c.f. last paragraph of Functors and Type Abstraction).
Question: Sometimes I may want this to pass type checking because for my purposes they can be considered equal. Is there a way to relax this?
Using gasche's suggestion of removing coercion, the above code can be written as
module type FOO = sig type e end
module type BAR = sig type t end
module Foo = struct type e = int end
module Bar = struct type t = int end
module Fun (F : FOO with type e=int) (B : BAR with type t = int) = struct type x = F.e * B.t end
module Bla = Fun (Foo) (Bar)
which compiles fine. Strangely, I get
# let f x : Bla.x = (x,x);;
val f : Foo.e -> Bla.x = <fun>
Question: why does it infer that x is Foo.e? It could as well be Bar.t?
The problem is how you define Foo and Bar : module Foo : FOO = .... By imposing this signature here, you "seal" the module and make the type abstract. It cannot be reverted. You should remove the : FOO coercion here, and use it later when you need the abstraction. You could also use module Foo : (FOO with type e = int) = ....
I'm not sure how the printer chooses amongst equal types, but in this case you can cause it to print a different name by explicitly annotating your function argument:
# let f (x:Bar.t) : Bla.x = (x,x);;
val f : Bar.t -> Bla.x = <fun>