This shows you the differences between two versions of the page.
— |
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. | ||