7
votes

I am trying to define a type (structure?) in Julia for a vector with three elements. I think the closest thing I found Optimally passing dimensions of fixed size array in julia and Declare the size of an array attribute in a type definition, however these are pre-0.6 since immutables are no longer a thing. Also, it just seems wrong.

The use case is that I know the size of the vectors my function will be taking and want to have

function myFunc(v::threeVec,u::threeVec)
    Do stuff to u and v
end

Further searching led me constructors. https://docs.julialang.org/en/stable/manual/constructors/ In particular I saw the example

struct OrderedPair
           x::Real
           y::Real
           OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
       end

However this is a separate object, and even then, I'm not sure how I'd pass something like that into a function. I considered instead using triples since those have type Tuple(Int,Int,Int) however I'm going to be doing vector/matrix arithmetic on u and v so I'm rather not have to convert them.

I could have the vector length checked inside the function, but I read in the tips that its preferred that you use types for this due to the dispatcher. For this particular function, that is a reasonable way of doing it in this case, but in other use cases this might not be such a great idea so I'd like to do it the "right way" now.

1
I would recommend you to check out github.com/JuliaArrays/StaticArrays.jl if it would suit your needs.Bogumił Kamiński
That is definitely useful, but I was hoping to pass "normal" vectors in without making any special declarations on bindings.Nero gris
StaticArrays.jl is definitely the way to go. You can have a type alias for a static array of length 3, call it ThreeVec as in your example. You could implement a type yourself, but it seems unnecessary given the many array packages out there.juliohm
If someone could make a simple working example, I'd gladly make it as the answer, however my own work with StaticArrays was less than stellar.Nero gris
Can you explain what you feel is missing or is inconvenient with StaticArrays. I find it to be a fantastic package, so I'm curious what you don't like about it.DNF

1 Answers

8
votes

There are many ways to deal with this type of scenario; I'll outline a few of them below based on the ideas you provided.

Option 1: StaticArrays

The StaticArrays.jl package provides support for fixed-length arrays.

using StaticArrays
const ThreeVec{T} = SVector{3,T}

function myFunc(u::ThreeVec, v::ThreeVec)
    u .+ v  # example functionality
end

This implementation allows myFunc to be called only when both arguments are SVectors of length 3.

julia> myFunc(SVector(1, 2, 3), SVector(4, 5, 6))
3-element SArray{Tuple{3},Int64,1,3}:
 5
 7
 9

julia> myFunc(SVector(1, 2, 3), SVector(4, 5))
ERROR: MethodError: no method matching myFunc(::SArray{Tuple{3},Int64,1,3}, ::SArray{Tuple{2},Int64,1,2})
Closest candidates are:
  myFunc(::SArray{Tuple{3},T,1,3} where T, ::SArray{Tuple{3},T,1,3} where T) at REPL[13]:2

It's also possible to define a custom type with an inner constructor to assert that the length is correct. However, in addition to potential inefficiencies, this will require that you overload various methods to support your custom type, which StaticArrays already handles.

Option 2: Tuples

Depending on what vector/matrix arithmetic you plan on performing, it's possible that tuples will already support the functionality natively through broadcasting. For example, although tuples can't be added, addition can be broadcasted over their elements.

julia> u = (1, 2, 3);
       v = (4, 5, 6);

julia> u + v  # not allowed
ERROR: MethodError: no method matching +(::Tuple{Int64,Int64,Int64}, ::Tuple{Int64,Int64,Int64})
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:502
Stacktrace:
 [1] top-level scope at none:0

julia> u .+ v  # element-wise broadcasting
(5, 7, 9)

Option 3: Runtime Error

If you would like to operate on the built-in Vector type, you can simply throw an error whenever the input is invalid, moving your error handling from compilation to runtime.

function myFunc(u::Vector, v::Vector)
    length(u) == 3 || throw(ArgumentError("Invalid length of (u = $u), should be 3"))
    length(v) == 3 || throw(ArgumentError("Invalid length of (v = $v), should be 3"))
    u .+ v  # example functionality
end