User Tools

Site Tools


structs

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

structs [2018/12/28 11:19] (current)
Line 1: Line 1:
 +
 +~~CLOSETOC~~
 +
 +~~TOC 1-3 wide~~
 +
 +```juliarepl
 +julia> pkgchk.( [ "​julia"​ => v"​1.0.3"​ ] );
 +```
 +
 +# Structs
 +
 +- The julia documentation refers to '​struct'​s as `composite` types. ​ (There are also composite type types, but we will skip these for now.)
 +
 +- Once declared, Structs never have **mutable** keys (field names). ​ The **mutable** keyword before the struct states that values held in the keys can be changed.
 +
 +- Structs often have a `mutable` in front of them, because this makes it possible to change their values (not keys) after their initialization.
 +
 +- A [[dicts|Dictionary]] is logically similar---it is like a run-time struct. ​ However dictionary access is syntactically very different: it uses `d["​key"​]` instead of `d.key`.
 +
 +  - Dictionaries are accessed via `[member]`, while structs are accessed via `.member`.
 +
 +
 +## Defining a (Nested) Struct
 +
 +```juliarepl
 +julia> struct SI; si::Int8; end#struct
 +
 +julia> struct SE
 +             ​v1::​Int32;​ v2::​Float64;​
 +             ​v3::​SI;​
 +       ​end#​struct##​
 +```
 +
 +- The type syntax for structs is unusual. ​ Unlike other types which can be defined as, say, `v= Vector{Int8}`,​ it is not possible to define a struct with `v=struct; si::Int64; end`.  Instead, the correct syntax is `struct v; si::Int64; end`.
 +
 +- Internally, Julia will maintain a pointer to the SI object in an objects SE.v3; but whenever SE.v3 is used, it is *always* dereferenced. ​ As always, `copy()` and `deepcopy()` can make complete copies. ​ The programmer needs to remember whether copies or near copies refer to the very same identical data (i.e., memory), or to a copy.
 +
 +- After assignment, SI and SE objects will be read-only. ​ Place a `mutable` in front of the struct if you want to change the contents of the struct later on.
 +
 +
 +
 +## Showing the Struct Definition
 +
 +```juliarepl
 +julia> struct SI; si::Int8; end;   ​struct SE; v1::Int32; v2::​Float64;​ v3::SI; end#struct
 +
 +julia> dump(SE) ​                   ## the universal inquisitor
 +SE <: Any
 +  v1::Int32
 +  v2::Float64
 +  v3::SI
 +
 +julia> fieldnames(SE) ​             ## introspection
 +(:v1, :v2, :v3)
 +
 +```
 +
 +
 +
 +
 +## Initializing and Initializers
 +
 +Most custom initializations in Julia are called outer initializations,​ because they are written as functions outside the actual structure definition. ​ This means that the outer program must know the struct definition and just assigns inputs according to content types. ​ For example,
 +
 +```juliarepl
 +julia> struct SI; si::Int8; end; ## inner structure
 +
 +julia> struct SE; v1::Int32; v2::​Float64;​ v3::SI; end#​struct ##​ nested structure
 +
 +julia> se= SE(12, 12.2, SI(2)) ​    ​ ##​ uses the automatic default constructors
 +SE(12, 12.2, SI(2))
 +
 +julia> SE( x::Float64 )= SE( trunc(x), x, SI( trunc(x)*2 ) ) ## defines a custom outer initializer function
 +SE
 +
 +julia> x= SE( 5.2 )                                             ## and uses it
 +SE(5, 5.2, SI(10))
 +```
 +
 +Here is an example of multiple types of initializers:​
 +
 +```juliarepl
 +julia> mutable struct ST; f1::​Float64;​ i1::Int64; end#struct
 +
 +julia> function ST( in1::​Float64 )
 +    ​@assert (in1 >= 1.0) && (in1 <= 10.0)      ## Example of basic creation-time range-checking
 +           ST( Float64(in1),​ Int64(-1) )          ## now calls the default constructor
 +       ​end#​function##​
 +ST
 +
 +julia> function ST( in1::Int64 )
 +     @assert( (in1 >= 0) && (in1 <= 5) )
 +            ST( Float64(NaN),​ Int64(in1) )        ## now calls the default constructor
 +       ​end#​function##​
 +ST
 +
 +julia> ST(1.2) ##​ calls the first constructor
 +ST(1.2, -1)
 +
 +julia> ST(1.2, 22) ## calls the automatic constructor
 +ST(1.2, 22)
 +
 +julia> ST(22) ##​ calls the second constructor,​ which does not like 22
 +ERROR: AssertionError:​ in1 >= 0 && in1 <= 5
 +Stacktrace:
 +
 +```
 +
 +
 +WARNING Unlike other languages, Julia discourages inner initializers. ​ You need them only when you want to override the default constructor. ​ There is an example in the appendix.
 +
 +
 +
 +
 +## Accessing Value of a Key/Field in a Struct
 +
 +```juliarepl
 +julia> struct SE; v1::Int16; end#​struct ##​ define the sample structure type
 +
 +julia> se= SE(12); ​                             ## create the object of this type, and assign value
 +
 +julia> se.v1                                   ## retrieve the value in the object
 +12
 +```
 +
 +
 +
 +## Various Operations on Structs
 +
 +### Collecting all Values of a Particular Fields of Vector of Structs
 +
 +```juliarepl
 +julia> struct S; v::Int; s::String; end#struct
 +
 +julia> svec= Vector{S}( [ S(1,"​one"​),​ S(2,"​two"​),​ S(3,"​three"​) ] )
 +3-element Array{S,1}:
 + S(1, "​one"​)
 + S(2, "​two"​)
 + S(3, "​three"​)
 +
 +julia> [ svec[i].v for i=1:​length(svec) ] ## these "​comprehensions"​ construct vectors
 +3-element Array{Int64,​1}:​
 + 1
 + 2
 + 3
 + 
 +julia> [ svec[i].s for i=1:​length(svec) ]
 +3-element Array{String,​1}:​
 + "​one"​
 + "​two"​
 + "​three"​
 +
 +```
 +
 +### Eliminating Duplicates From Vector of Structs
 +
 +
 +```juliarepl
 +julia> struct S; x::Int; s::String; end#struct
 +
 +julia> svec= [ S(1, "​one"​),​ S(2, "​twoa"​),​ S(2, "​twob"​),​ S(2, "​twoc"​),​ S(3, "​three"​) ]
 +5-element Array{S,1}:
 + S(1, "​one"​)
 + S(2, "​twoa"​)
 + S(2, "​twob"​)
 + S(2, "​twoc"​)
 + S(3, "​three"​)
 +
 +julia> unique( v->v.x, svec )
 +3-element Array{S,1}:
 + S(1, "​one"​)
 + S(2, "​twoa"​)
 + S(3, "​three"​)
 +
 +```
 +
 +* You could require that both `v.x` and `v.s` are unique, e.g., by creating a concat code from the two.
 +
 +
 +### Keeping The N "​Lowest"​ Structs in Vector of Structs
 +
 +```juliarepl
 +julia> struct S; x::Int; s::String; end#struct
 +
 +
 +julia> svec= [ S(10, "​A"​),​ S(-2, "​B"​),​ S(3, "​C"​),​ S(2, "​D"​),​ S(3, "​E"​) ]
 +5-element Array{S,1}:
 + S(10, "​A"​)
 + S(-2, "​B"​)
 + S(3, "​C"​)
 + S(2, "​D"​)
 + S(3, "​E"​)
 +
 +julia> xonly = map( i -> svec[i].x, 1:​length(svec) ) ## x is our "​Low"​ Criterion
 +5-element Array{Int64,​1}:​
 + 10
 + -2
 +  3
 +  2
 +  3
 +
 +julia> newvec= svec[ sortperm( xonly ) ][1:2] ## 2 is our number that we want to retain
 +2-element Array{S,1}:
 + S(-2, "​B"​)
 + S(2, "​D"​)
 +
 +```
 +
 +* you could also define operators for S structs, and use this directly.
 +
 +
 +
 +## Interrogating Mixed Data Struct
 +
 +The `dump` function works not only on struct types, but also on struct objects with values:
 +
 +```juliarepl
 +julia> struct SI; si::Int8; end;##​struct ​       ## the inner type
 +
 +julia> struct SE; v1::Int32; v2::​Float64;​ v3::SI; end;#​struct##​ ##​ the outer type
 +
 +julia> se= SE(12, 12.2, SI(2));​ ##​ the (nested) object
 +
 +julia> dump(SE) ##​ the type
 +SE <: Any
 +  v1::Int32
 +  v2::Float64
 +  v3::SI
 +
 +julia> dump(se) ##​ the object
 +SE
 +  v1: Int32 12
 +  v2: Float64 12.2
 +  v3: SI
 +    si: Int8 2
 +```
 +
 +
 +
 +## Important Reminder: Copies and Deepcopies
 +
 +See also [[arraysintro|Arrays]] and [[dicts|Dictionaries]].
 +
 +
 +### Only Assignment (Alias) Can Alter Referenced Structs
 +
 +```juliarepl
 +julia> mutable struct Inner; i::​Vector{Int};​ end#struct Inner
 +
 +julia> mutable struct Outer; o::​Vector{Int};​ ri::Inner; end#struct Outer ## nested
 +
 +julia> original= Outer( [1,2], Inner( [3,4] ) ) ## create an Outer object
 +Outer([1, 2], Inner([3, 4]))
 +
 +julia> asgn= original ##​ both asgn and original are aliases to the same struct
 +Outer([1, 2], Inner([3, 4]))
 +
 +julia> asgn.o= [5,6]; asgn.ri.i= [7,8]; ## tinker with asgn...
 +
 +julia> original ##​ ...and it changes the origina, too.
 +Outer([5, 6], Inner([7, 8]))
 +
 +```
 +
 +### Copy and Deepcopy
 +
 +```juliarepl
 +julia> mutable struct Inner; i::​Vector{Int};​ end#struct Inner
 +
 +julia> mutable struct Outer; o::​Vector{Int};​ ri::Inner; end#struct Outer
 +
 +julia> original= Outer( [1,2], Inner( [3,4] ) ) ## all as in the previous example
 +Outer([1, 2], Inner([3, 4]))
 +
 +julia> cp= copy(original) ##​ impossible without custom copy method because of nesting
 +ERROR: MethodError:​ no method matching copy(::​Outer)
 +Closest candidates are:
 +  copy(!Matched::​Expr) at expr.jl:36
 +  copy(!Matched::​BitSet) at bitset.jl:​46
 +  copy(!Matched::​Markdown.MD) at /​Users/​osx/​buildbot/​slave/​package_osx64/​build/​usr/​share/​julia/​stdlib/​v1.0/​Markdown/​src/​parse/​parse.jl:​30
 +  ...
 +Stacktrace:
 +
 +julia> dpcp= deepcopy(original) ##​ but deepcopy still works
 +Outer([1, 2], Inner([3, 4]))
 +
 +julia> dpcp.o= [5,6]; dpcp.ri.i= [7,8] ; ## try this
 +
 +julia> dpcp.o[2]=(-7);​ dpcp.ri.i[2]=(-9);​ ##​ or this,
 +
 +julia> original ##​ but the original remains unaffected
 +Outer([1, 2], Inner([3, 4]))
 +```
 +
 +
 +
 +#### Defining Broadcast (Facilitating Dot Assignments for Structures)
 +
 +Ordinary assignment means that a and b point to the same object. ​ Assigning to one changes the other:
 +
 +```juliarepl
 +julia> mutable struct Point; x::Float64; y::Float64; end#​struct##​
 +
 +julia> a= Point( 1.0, 2.0 ); b= Point( 3.0, 4.0 );
 +
 +julia> a= b;   b.x= 5.0; a ## only one point is left, other to be garbage collected
 +Point(5.0, 4.0)
 +```
 +
 +(Note that the julia `Pair{Float64,​Float64}` type would probably be more suitable.)
 +
 +It is possible to define dot operators for structs to allow element-wise and/or copy operations, like `.=` (which should presumably copy the contents of one struct to another):
 +
 +```juliarepl
 +julia> mutable struct Point; x::Float64; y::Float64; end#struct
 +
 +julia> function Base.broadcast!(::​typeof(identity),​ dest::​Point,​ src::Point)
 +     dest.x= src.x
 +     dest.y= src.y
 +     dest
 + end;#​function##​
 +
 +julia> a= Point( 1.0, 2.0 ); b= Point( 3.0, 4.0 );
 +
 +julia> a.= b;
 +
 +julia> b.x= 5.0; a ## both points remain. ​ contents of b were copied into a
 +Point(1.0, 2.0)
 +
 +```
 +
 +- Be careful about how deep the broadcast copies
 +
 +
 +### Comparing Structs
 +
 +WARNING Easy to get wrong
 +
 +
 +```juliarepl
 +julia> struct I; x::Int; end;
 +
 +julia> I(1) == I(1)
 +true
 +
 +julia> mutable struct M; x::Int; end;
 +
 +julia> M(1) == M(1)
 +false
 +
 +```
 +
 +
 +
 +
 +## Parametric Composite Types (Abstract Types, Ala C++ STL)
 +
 +Structures can be defined to hold all sorts of types, just like [[arraysintro|Array]] or [[dicts|Dictionaries]] can be containers for different content types. ​  The ideas are similar to the functionality in the C++ STL.
 +
 +```juliarepl
 +julia> struct Point{T}; ​ x::T; y::T; end#​struct ##​ Point Contents could take any type, truly generic
 +
 +julia> PointFloat= Point{Float32};​ ##​ Pointfloat is a type 
 +
 +julia> p= PointFloat(1.0,​2.0) ##​ Point{Float32} is a type, p is a variable
 +Point{Float32}(1.0f0,​ 2.0f0)
 +
 +julia> p= Point{Int8}(1.0,​2.0) ##​ this Point type is for Int8'​s. ​ p is an object
 +Point{Int8}(1,​ 2)
 +
 +julia> p= Point{Int8}(1.5,​2.0) ##​ you cannot assign Float contents to an Int8's Point.
 +ERROR: InexactError:​ Int8(Int8, 1.5)
 +Stacktrace:
 +
 +julia> Point{Float16} <: Point ## julia knows that the specific Point type belong to Point
 +true
 +
 +julia> Point{Float16} <: Point{Float32} ##​ but julia does not know about the eltype hierarchy.
 +false
 +
 +```
 +
 +
 +With the `Point` type, you could now define [[fundispath|methods]] like
 +
 +```juliarepl
 +julia> function normed_distance(p::​Pair{Real,​Real});​ sqrt(first(p)^2+last(p)^2);​ end#​function#​
 +normed_distance (generic function with 1 method)
 +
 +```
 +
 +Sometimes your function needs to use information from the specific type that your generic function caught. ​ In this case,
 +
 +```julianoeval
 +julia> function normed_distance(p::​Pair{T,​T})::​T where T<:Real; sqrt(first(p)^2+last(p)^2);​ end#​function
 +normed_distance (generic function with 1 method)
 +
 +julia> normed_distance( Pair( 2, 3.0 ) )
 +ERROR: MethodError:​ no method matching normed_distance(::​Pair{Int64,​Float64})
 +Closest candidates are:
 +  normed_distance(::​Pair{T<:​Real,​T<:​Real}) where T<:Real at REPL[2]:1
 +Stacktrace:
 +
 +```
 +
 +The latter works only if the pair has the same inputs.
 +
 +
 +
 +## Extending Structures (Inheritance)
 +
 +Full inheritance is not natively supported in Julia. ​ However, it is possible to define abstract types, and to use the macro facility to accomplish similar functionality.
 +
 +```juliarepl
 +julia> macro shared_fields_mammals() ##​ all mammals should have a weight field
 +    ​return esc(:(
 +    ​weight::​Int;​
 + ))
 + end#​macro##​
 +@shared_fields_mammals (macro with 1 method)
 +
 +julia> abstract type Mammal end
 +
 +julia> mutable struct Giraffe <: Mammal ## a giraffe is now a struct that belongs to mammals
 +     @shared_fields_mammals ##​ add the weight field
 +     spots::Int
 + end#​struct##​
 +
 +julia> mutable struct Elephant <: Mammal
 +     @shared_fields_mammals ##​ add the weight field
 +     fatratio::​Float64
 + end#​struct##​
 +
 +julia> function fatten(animal::​T,​ w::Int) where {T<:​Mammal} ##​ fatten works on any mammal
 +     animal.weight += w
 +            animal
 + end#​function##​
 +fatten (generic function with 1 method)
 +
 +julia> a1= Giraffe( 400, 10 )
 +Giraffe(400,​ 10)
 +
 +julia> fatten( a1, 20 )
 +Giraffe(420,​ 10)
 +
 +julia> a2= Elephant( 800, 0.2 )
 +Elephant(800,​ 0.2)
 +
 +julia> fatten( a2, 50 )
 +Elephant(850,​ 0.2)
 +
 +```
 +
 +
 +
 +
 +
 +## Data Encapsulation
 +
 +Hiding the internals of structs is *not* supported by Julia.
 +
 +
 +
 +
 +# Backmatter
 +
 +## Useful Packages on Julia Repository
 +
 +## Notes
 +
 +- It is possible but tedious to define two structs, where one extends the other by extra fields. ​ The relationship between the two is then made clear with an `abstract` and `<:` declaration.
 +
 +- See also `show()` for custom printing of structs in [[fileio#​personalized_printing|File IO]].
 +
 +- There is a bizarre reason for the `new()` inside inner constructors,​ which has to do with an ability to replace `new()`.
 +
 +- One may need uninitialized structs to construct self-referential objects. ​ See the manual, Chapter 16.3 of julia 0.6.2.
 +
 +- `promote` can often be used in structs for initializers.
 +
 +## References
 +
 +
 +
 +# Appendix
 +
 +## An Inner Initializer
 +
 +WARNING Unlike other languages, Julia discourages inner initializers. ​ You need them only when you want to override the default constructor. ​ There is an example in the appendix.
 +
 +```juliarepl
 +julia> mutable struct SE
 +           ​f1::​Float64
 +           ​i1::​Int64
 +
 +           ​function SE( in1::​Float64,​ in2::Int64 )        ## an inner constructor
 +               ​println("​calling SE inner"​)
 +               new( Float64(in1+10),​ Int64(in2+20) )      ## IMPORTANT: Inner must use `new`
 +           ​end#​function
 +       ​end#​struct##​
 +
 +julia> function SE( in1::​Float64 )
 +               ​println("​calling SE outer"​)
 +               SE( Float64(in1),​ Int64(-1) )         ## now calls the inner constructor
 +           ​end#​function##​
 +SE
 +
 +julia> m= SE(2.0, 8); dump(m)
 +calling SE inner
 +SE
 +  f1: Float64 12.0
 +  i1: Int64 28
 +
 +julia> m= SE(2.0); dump(m) ​                           ## good initializations
 +calling SE outer
 +calling SE inner
 +SE
 +  f1: Float64 12.0
 +  i1: Int64 19
 +
 +```
 +
 +* Once you define an inner (non-default) constructor,​ then Julia no longer defines an inner default constructor. ​ It is then up to you to define it.
 +
 +
 +
 +## "​Extending"​ Built-In Primitive Types with Structs (E.g., Limited Numeric Types)
 +
 +Though tempting, it is rarely useful for an end-user program to try to define more limited custom numeric types than the standard built-in primitives (e.g., `UInt8`). ​ There is no speed gain, either. ​ Here is an example of a FiveToTwenty limited numeric type that can, on demand, interoperate with Int64s:
 +
 +```juliarepl
 +julia> struct FiveToTwenty <: Unsigned
 +           ​val::​UInt8
 +           ​function FiveToTwenty(val::​Number)
 + @boundscheck begin @assert val >= 5 && val <= 20 end
 + new(UInt8(val))
 +           ​end#​function
 +       ​end#​struct##​
 +
 +julia> Base.show(io::​IO,​ v::​FiveToTwenty) = print(io, v.val)
 +
 +julia> FiveToTwenty(4)
 +ERROR: AssertionError:​ val >= 5 && val <= 20
 +Stacktrace:
 +
 +julia> FiveToTwenty(14)
 +14
 +
 +julia> Base.promote_rule(::​Type{Int},​ ::​Type{FiveToTwenty}) = Int
 +
 +julia> Base.convert(::​Type{Int},​ x::​FiveToTwenty) = Int(x.val)
 +
 +julia> dump( FiveToTwenty(13) + 10 )                ## note the result type is now Int64
 +Int64 23
 +
 +```
 +
 +See also [https://​discourse.julialang.org/​t/​floats-or-integers-or-vectors-with-specific-ranges/​8682/​7]
 +
 +# To Integrate
 +
 +
 +From https://​discourse.julialang.org/​t/​struggling-with-a-point-type-for-several-dimensions/​18066/​18
 +
 +To implement an x,y point type, wrap a tuple, which can be parametrized by the length:
 +
 +```julianoeval
 +julia> struct Point{S}
 +           ​x::​NTuple{S,​Float64}
 +       end
 +
 +julia> p = Point{2}((1.0,​ 2.0))
 +Point{2}((1.0,​ 2.0))
 +
 +julia> p = Point{3}((1.0,​ 2.0, 3.0))
 +Point{3}((1.0,​ 2.0, 3.0))
 +
 +```
 +
 +Tensors.jl 13 for tensor operations like dot, inner/outer products etc.
 +
 +StaticArrays.jl 4 for general fixed size arrays.
 +
 +```julianoeval
 +julia> using StaticArrays
 +
 +julia> struct PointType{S}
 +           ​data::​MVector{S,​Float64}
 +       end
 +
 +julia> PointType(args...) = PointType(MVector(args...))
 +PointType
 +
 +julia> tt = PointType(2.,​3.,​4.)
 +PointType{3}([2.0,​ 3.0, 4.0])
 +
 +julia> tt.data[2] = 0.
 +0.0
 +
 +julia> tt
 +PointType{3}([2.0,​ 0.0, 4.0])
 +```
 +
 +or
 +
 +```julianoeval
 +julia> using StaticArrays
 +
 +julia> struct Point3D <: FieldVector{3,​ Float64}
 +           ​x::​Float64
 +           ​y::​Float64
 +           ​z::​Float64
 +       end
 +
 +julia> p = Point3D(1,​2,​3)
 +3-element Point3D:
 + 1.0
 + 2.0
 + 3.0
 +
 +julia> p2 = setindex(p, 5, 1)
 +3-element Point3D:
 + 5.0
 + 2.0
 + 3.0
 +```
 +
 +StaticArrays are so fast, you can often come out ahead by using StaticArrays even if you are looking for a mutable array. ​
  
structs.txt ยท Last modified: 2018/12/28 11:19 (external edit)