Structures
Structures are data records of fields.
A name and a set of fields is required in order to define a structure. Structures (that do not derive) cannot be empty - have no fields.
MyStruct
field1: U8
field2: Str
Note that the name of the structure has to start with a capital letter, because it is a new type. Also note the absence of any specific keywords like ‘struct’.
Here is how to create an instance of a structure and assign its fields with values.
MyStruct
field1: U8
field2: Str
s : MyStruct =
field1 = 42
field2 = "42"
// alternative
s : MyStruct = { field1 = 42, field2 = "42" }
// MyStruct : MyBase
s : MyBase = MyStruct
field1 = 42
field2 = "42"
A structure can have default values. If no value is explicitly specified each type is initialized with its #default
value:
MyStruct
field1: U8 = 42
field2: U8 = 42
s : MyStruct =
field2 = 101 // overwrites any value
// s.field1 = 42
// s.field2 = 101
MyStruct
field1: U8
field2: Str
fn: (p: MyStruct): U8
...
// syntax?
fn({field1 = 42, field2 = "42"})
fn(MyStruct
field1 = 42
field2 = "42")
Composition
New structure types can be made from other structure types by the following methods:
- Inheritance
- Containment
…or a combination of these.
Here is an example of inheritance:
MyStruct
field1: U8
MyDerived: MyStruct
field2: Str
MyDerived
has two fields: field1
and field2
. An instance of the MyDerived
structure can be treated as an instance of the MyStruct
structure.
Inheritance makes the structure bigger.
Multiple inheritance is not supported at this moment.
TBD: default to ‘closed’ structured and explicitly ‘opening’ them up for inheritance. Syntax is iffy.
// Any looks weird
MyStruct: Open<>
MyStruct: Open<Any>
...
OpenDerived: Open<MyStruct>
...
// closed even if MyStruct is open
ClosedDerived: MyStruct
...
Here is an example of containment:
MyStruct
field1: U8
MyContainer
cnt: MyStruct
field2: Str
The MyContainer
structure still has the field1
from MyStruct
but it has to be reached by the cnt
field. Here is how the structure would be initialized:
s : MyContainer =
cnt.field1 = 42
field2 = "OK"
Unions in structures are not supported at this moment.
Bit Fields
This needs to be revised for .NET interop.
A structure can contain bit fields using the Bit<T>
type.
So the following example will only take up 1 byte:
MyStruct
field1: Bit<2> // bit 0-1
field2: Bit<3> // bit 2-4
field3: Bit<3> // bit 5-7
Bit fields can be combined with other types.
MyStruct
field1: Bit<2>
field2: Bit<4>
field3: Bit<4>
other: U8
field4: Bit<4>
field5: Bit<4>
The resulting structure has 3 bytes worth of bit fields and one byte for the other field.
What will be the #offset for a bit field? (byte-offset/bit-offset)
Should this be a union type?
Nested Declaration
Structures can be declared in a nested fashion:
Struct
nested: NestedStruct
fld1: U8
fld2: Str
name: Str
However the NestedStruct
type is not available outside the structure it was declared in.
TBD: Can the nested type be anonymous?
Struct
nested
fld1: U8
fld2: Str
name: Str
Nested inside a function? (yes)
someFn: (p: U8)
LocalStruct // only visible inside this function
fld1: U8
fld2: Str
s : LocalStruct = // use it
fld1 = 42
fld2 = "42"
...
Field Indexing
Ways in which to access fields for reading or writing.
MyStruct
fld1: U8
fld2: Str
s = Mut<MyStruct> = // mutable
fld1 = 42
fld2 = "42"
f1 := s.fld1 // dot notation
f2 := s[MyStruct#fld2] // indexed by name at compile time (metadata)
f1 := s["fld1"] // indexed by name at run time
// Error or Opt, if field with 'name' does not exist? What if the field type is incompatible with the variable type? Any?
name = ...
f2 := s[name] // indexed by name at runtime time/reflection (variable)
s[name] = 42
// Error if field with 'name' does not exist? What if the field type is incompatible with the value type? Auto-Convert?
Syntax should be the same as with the Dyn
amic Type.
Structure Layout
The fields of a structure are layed out in the most optimal order. Every field’s datatype has a specific byte alignment and intrinsic size. Ordering fields randomly may lead to excessive filler bytes in order to meet alignment requirements. The Z# compiler will generate a StructLayoutAttribute(LayoutKind.Auto)
attribute for each struct. A StructLayoutAttribute
can be used in the code to disable / override this reordering.
The
.NET
compiler does the actual layout in memory.
Tables
Find a way to allow to easily define tables of data using struct types in an array.
MyStruct
fld1: U8
fld2: Str
arr: Array<MyStruct> = (
{ fld1 = 42, fld2 = "42" }, // by name
{ 101, "101" }, // in field order
)
If we allow this, then we can also move towards easy structure instantiation (ala JS/Json).
MyStruct1
fld1: U8
fld2: Str
MyStruct2
first: MyStruct1
second: MyStruct1
// TODO: a different syntax is used here!
// in order
v : MyStruct2 =
{ 42, "42" }
{ 101, "101" }
// by name
v: MyStruct2 =
first = { fld1 = 42, fld2 = "42" }
second = { fld1 = 101, fld2 = "101" }
// structured by name
v: MyStruct2 =
first =
fld1 = 42
fld2 = "42"
second =
fld1 = 101
fld2 = "101"
// or any combination of these??
Anonymous Structures
Also known as Tuples.
.NET
makes a distinction between (two types of) tuples and anonymous types - Z# does not. We do need to choose how Z# is going to leverage these .NET Tuples in the code generation.
Use
{}
for object creation syntax.
a := { Fld1 = 42, Fld2 = "42" }
// a is a tuple with two fields
x := a.Fld1 // U8
y := a.Fld2 // Str
// deconstruct - creates new vars
(fld1, fld2) := a // -or-
fld1, fld2 := a
fld1, fld2 := ...a // spread operator?
// build new tuple from vars
b := { fld1, fld2 }
same := (a = b)
// true: compared on value semantics
Preferred is to use field names for tuples, but even those can be omitted but then deconstruction has to be used to unpack.
// no structure type name, no field names
x := { 42, "42" }
// C# does this (not for ValueTuple though):
x.Item1 // Error: Item1 does not exist
// to use, must deconstruct in order
(n, s) := x // -or-
n, s := x
// n = 42 (U8)
// s = "42" (Str)
This is how you reference anonymous types in code.
// have to repeat the structure of the type
anoStructFn: (s: (number: U8, name: Str))
// if the anonymous type also has no field names
anoStructFn: (s: (U8, Str))
We want to line up the syntax (and semantics) with the parameters of a function call. Adopting a global rule that ‘field-lists’ (tuples, function params, deconstructs etc) can be build in-order or named or a combination (see function parameters). Array initialization too?
arr = (1, 2, 3, 4)
- has no field names!
Transformation (Mapping)
TBD
Allow fields of one structure to be mapped easily to fill fields of another structure.
What operator to use?
<=
?
s1 : Struct1
// manual
s2 : Struct2 =
fld1 = s1.x
fld2 = s1.y
// by convention
s2: Struct2 <= s1
Transform using a custom Type and rules/constraints?
Requires some library plumbing code.
// marker type for compiler
Transform<T1, T2>
...
// these functions are templates where the mapping rules are compiled into
transform: <T1, T2>(self: Transform<T1, T2>, source: T1): T2
...
reverseTransform: <T1, T2>(self: Transform<T1, T2>, source: T2): T1
...
// Transform is the compiler supported marker type.
MapS1ToS2: Transform<Struct1, Struct2>
#Struct2.fld1 = #Struct1.x
#Struct2.fld2 = #Struct1.y
// transform: <T: Transform<S, D>, S, D>(self: T, source: S): D
s2 := MapS1ToS2.transform(s1)
// reverseTransform: <T: Transform<S, D>, S, D>(self: T, source: D): S
s1 := MapS1ToS2.reverseTransform(s2)
// additive api: allows parameters to be passed to multiple transformations
// transform: <T: Transform<S, D>, S, D>(self: T, source: S, destination: D)
MapS1ToS2.transform(s1, s2)
// reverseTransform: <T: Transform<S, D>, S, D>(self: T, source: D, destination: S)
MapS1ToS2.reverseTransform(s2, s1)
// Transform could support all kinds of hooks (overrides)
afterTransform: (self: MapS1ToS2)
self.Target.fld3 = self.Source.z.Str()
// execution of these hooks would not be obvious!
Rule base mapping could use specific operators to indicate what rules to use for normal or reverse mapping.
// Transform<Source, Destination>
MapStruct1Struct2: Transform<Struct1, Struct2>
#Struct2.fld1 <= #Struct1.x // src => dest
#Struct2.fld2 <=> #Struct1.y // src => dest and reverse
#Struct2.fld3 => #Struct1.z // dest => src
TBD: More complex mappings:
- Splitting or joining fields?
- Conditional mapping/logic?
- External dependencies?
- Calling helpers for transformation?
- Nested objects/object trees?
CustomFn1: (Struct1 self): U16
...
Deconstruct2
x: U16
z: U16
CustomFn2: (Struct2: self): Deconstruct2
...
CustomFn3: (Struct1: self, x: U16, y: U16)
...
// Transform<Source, Destination>
MapStruct1Struct2: Transform<Struct1, Struct2>
// CustomFn1 yields mapping result
#Struct2.fld1 <= #Struct1.CustomFn1
// CustomFn2's result is deconstructed onto target fields
#Struct2.CustomFn2 => #Struct1.x, Struct1.z
// CustomFn3 receives 2 parameters
#Struct2.CustomFn3 <= #Struct1.x, Struct1.y
MapStruct1Struct2: Transform<Struct1, Struct2>
// match fields by name (2-way)
#Struct1 <=> #Struct2
If no mapping rule structure is defined the field names are matched on a 1:1 basis in both directions using Identifier name matching rules.
Dependent Field Validation
(Dependent Types)
Add validation rules between fields. For example: #fld1 > fld2
When/how are these rules enforced?
MyStruct
fld1: U8
fld2: Str
// the length of the string must be less than value of fld1
#fld2.Length < fld1
TBD
- Have a different syntax for accessing mutable and immutable data. The source code will tell you explicitly. Not sure about the details.
- Allow YAML/JSON/XAML/XML to be used inline for declaring hierarchical data? Not sure how to separate the YAML/JSON/XAML/XML syntax from the Z# syntax.