I want to announce the VarStructs.jl package which introduces variables structs with dispatching and redefinition support.
using Pkg; Pkg.add("VarStructs")
using VarStructs
Features
VarStructs are similar to structs but have extra features:
- You can add fields after their definition.
- They can be defined inside a function or a local scope.
- They can be redefined.
Similar to structs
- They can be used for dispatching (zero-cost)
- They can have custom constructors
- They have type conversion/checking for the fields that are declared
Declaration
There are two ways to declare them:
Struct Syntax
In this syntax, providing initial values and types for the fields are optional.
@var struct Animal
name::String
number::Int64
end
Call Syntax
In this syntax, you should provide the initial values for the fields. Providing type for the fields are optional (if not provided, it is considered as Any
).
@var Person(
name = "Amin",
number::Float64 = 20.0,
)
Getting an Instance
Use the following syntax for getting an instance:
julia> person = Person(name = "Amin", number = 20.0)
Person(
name::Any = Amin,
number::Float64 = 20.0,
)
# Type conversion for the fields that were declared
julia> person2 = Person(name = "Amin", number = 20) # number is converted to Float64
Person(
name::Any = Amin,
number::Float64 = 20.0,
)
# Type checking for the fields that were declared
julia> person2 = Person(name = "Amin", number = "20")
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Float64
# new field added
julia> person2 = Person(name = "Amin", number = 20.0, initial = "T")
Person(
initial::String = T,
name::Any = Amin,
number::Float64 = 20.0,
)
The two syntaxes that are used for declaration also return an instance of the VarStruct. So if you need an instance right away, you can use the following. Note that in redeclaration you will not get type checking based on the previous declaration.
animal = @var Animal(
name = "lion",
number::Int64 = 10,
)
# redefinition of `Animal` returns a new instance:
animal2 = @var Animal(
name = "dog",
number::Int64 = 1,
)
Accessing, Setting, Adding Fields
# Accessing
julia> person.name
"Amin"
# Setting
julia> person.name = "Tim"
# Adding Fields
julia> person.initial = "T"
Dispatch
info
function dispatch for Person
and Animal
type:
function info(x::Person)
println("Their home is city")
return x.name
end
function info(x::Animal)
println("Their home is jungle")
return x.name
end
julia> info(person)
"Their home is city"
"Amin"
julia> info(animal)
"Their home is jungle"
"lion"
Custom Constructor
To define a custom constructor return an instance using the keyword method:
function Person(name, number)
return Person(
name = name,
number = number,
initial = name[1],
)
end
Person("Amin", 20.0)
Give me benchmarks:
The dispatching is zero-cost like a normal struct:
julia> using VarStructs
julia> @var struct A
end;
julia> @var struct B
end;
julia> dispacth_varstruct(x::A) = 1
dispath_varstruct (generic function with 1 method)
julia> dispacth_varstruct(x::B) = 2
dispath_varstruct (generic function with 2 methods)
julia> using BenchmarkTools
julia> @btime dispath_varstruct(x) setup=(x=rand()<0.5 ? A() : B())
0.001 ns (0 allocations: 0 bytes)