Question on RefValue

In this video here the lecturer defines a composite type to hold his model and to step it forward in time.

He needs to keep track of the number of iterations but the number of iterations is a field within a struct which means it is immutable (i.e. can’t change it once defined). Is that why he uses the RefValue thing? It allows him to somehow change its value even though it is a field of an immutable struct? He also incremented it in a way I’ve not seen before, something like `iter[ ] =+ 1’.

Lastly, why does he prefix it with base? Is base not imported by default?

1 Like

The immutability applies only to the struct itself, you can’t change what the fields point to. But you can l mutate the elements stored in the fields. So yes, that’s what the RefValue is for. I think Base.RefValue is the old syntax, now you can just use Ref

x+=1 is the same as x = x+1, this is a common syntax in many languages.


Define Base.blah() would really add method to function in Base instead of shadowing them:

julia> length(x) = 3
length (generic function with 1 method)

julia> length([1,2])
3

vs.

# don't do this in real life
julia> Base.length(x) = 3 #this is equivalent to x::Any

julia> length([1,2])
2

# this will dispatch to our new x::Any fallback
julia> length(DomainError)
3
1 Like

No, Ref is an abstract type, so although you can create a RefValue using Ref(x), if you are putting it inside a struct, you want to annotate the field as RefValue{T}, so it is concretely typed.

9 Likes

In that case, I guess what would have occurred to me naturally would have been to do the following, define it as iter::Int64 and then in the composite type have it do the following to keep track of the number of iterations S.iter += 1 Maybe the problem is that I can’t assign an initial value like 0 to a field.

Is that possible? I’m just wondering why even use the RefValue thing at all.

Huh thanks I must have used it the wrong way for a while then

You got it right in your first post: in an (immutable) struct, the field iter::Int64 can be 0 or any other Int64. But afterwards you cannot replace it by another. On the other hand, if the field is mutable (e.g. the first field of the example in the video, T::Array{Float64, 2}) you cannot replace it either, but you can mutate it. For instance, if you have:

struct Foo
    i::Int64
    a::Array{Int64, 2}
end

f = Foo(0, [1 2; 3 4])

Then you cannot do f.i = 1 or f.a = [5 6; 7 8]. But you can do f.a[1] = 0, because that is mutating the object referred to by the field a, not replacing it by another object. And you could also mutate it such that it looks as if you were actually replacing it too (watch the dot before the equal sign!):

f.a .= [5 6; 7 8]

Now, a RefValue is, roughly speaking, as a “one-slot” array, which cannot have more than one item of the specified type. That’s why it is indexed more or less as an array (but without any index between the brackets, because it would be superfluous). If iter is a RefValue, then iter[] means “the value that it contains”; iter[] = 1 means “mutate iter inserting 1 in its slot”; and as @jling told, iter[] += 1 is the same as iter[] = iter[] + 1.

8 Likes

Thank you - I understand now.

One last question though, why do I need to prefix RefValue with base? I’m pretty sure I use other functions from base without prefixing them.

Please see the second part of @jling 's post.

1 Like

Although @jling’s answer is correct, I’m afraid that it does not address @TI36XPro’s specific question. If I understand correctly, the question is why the field iter of the type ClimateModelSimulation in the linked video is annotated as Base.RefValue{Int64}, instead of just RefValue{Int64}.

The answer is this:

julia> RefValue
ERROR: UndefVarError: RefValue not defined

julia> Base.RefValue
Base.RefValue

So, although Base is indeed imported by default, that only affects names that are explicitly exported, and RefValue is not, so you must qualify it with the name of the module to access it.

8 Likes

Perfect, thank you. I suppose the next natural question would be why some are imported by default and others are not. Is there a list somewhere of the default imported functions from base?

It is even better than list defined by someone, you can use Julia functions to construct it!

According to View all functions exported in a package

julia> names(Base)
915-element Vector{Symbol}:
 :!
 :!=
 :!==
 ⋮

julia> names(Base, all = true)
5391-element Vector{Symbol}:
 :!
 :!=
 :!==
 ⋮

And for each exported symbol you can read documentation with the help of ?

julia> ? # you need to press question mark to switch to help mode
help?> !
search: ! != !== sum! put! pop! map! get! any! all! take! sort! read! push! prod! kron! fill! copy! conj! union! popat! merge! empty! count! clamp! unique! splice! resize! insert! filter!

  !(x)

  Boolean not. Implements three-valued logic (https://en.wikipedia.org/wiki/Three-valued_logic), returning missing if x is missing.
4 Likes

It’s an arbitrary decision of the developers. Internals that are not part of the API should of course not be exported. But there may be names of the API that it’s better to keep unexported too.
Exporting names has advantages (e.g. functions and types that are visible right after using the module), but also disadvantages (“crowded” namespaces, and potential conflicts between modules that export the same names). So it makes sense to export only the names of the part of the API that are meant to be used most often.

2 Likes