How to prevent global variable or array in module?

I am coming from Fortran to Julia. I know in Julia want to prevent using global variables.
But the thing is, how to prevent using global variables in a module?

For example, in the following module,

module Mod
global AAA=zeros(1000000000)

function f(x)
change the most up to date AAA with x in some way.
return nothing
end

function g(x)
change the most up to date AAA with x in some way.
return nothing
end

end

In the above example, I need to update the very big array AAA, so I put AAA in the global. So f(x) and g(x) can use the most updated AAA and further update them.

Now since Julia discourage the use of global variable, so in my case, what do I do?

I mean, do I put AAA just in the argument of f(x) and g(x) such that they become f(x,AAA) and g(x,AAA)?

Is passing a very big array like AAA in the argument really faster than putting AAA just as a global variable?

Thanks!

Not that it is strange per se, but it is strange that you need to do that in a module which is meant to be a reusable piece of code. Having a state within a module (library) is arguably bad.

That’s one way. Another way is to define a function that generates f and g on demand:

function fg_gen(AAA::AbstractArray)
    function f(x)
        change the most up to date AAA with x in some way.
        return AAA
    end

    function g(x)
        change the most up to date AAA with x in some way.
        return AAA
    end
    return f, g
end

In the client code, you then write f, g = fg_gen(zeros(1000000000)) and you’re good (you may sprinkle it with const if you define f and g in such a way globally).

No, it’s only as performant. Passing an array in Julia is really just passing a reference to that array. It’s not copying like in Fortran (now it comes to me why you keep asking “strange” questions).

4 Likes

Thank you very much!
You are absolutely right, I am Fortran guy and I am still thinking in Fortran way :slight_smile:
By the way, if possible, could you please let me know perhaps where can I find some high performance Julia code? Or, do you have some examples?
I really wish to see how people really write high performance Julia code. That can help a lot.

Fortran passes by reference as well.

@CRquantum: There is no “strange” way to write high peformant code in Julia (which is something good). Pedestrian, but adequate, Julia code, is performant, like in Fortran. One example (if I can have the audacity that a code of mine is performant): GitHub - m3g/CellListMap.jl: Flexible implementation of cell lists to map the calculations of particle-pair dependent functions, such as forces, energies, neighbor lists, etc.. You will see that such a code is in many ways very similar to what would be an equivalent Fortran code. Indeed, it would not take me more than a day to translate it from one language to the other.

Now if you want to see something that is more advanced in what concerns Julia usage, I like this one: GitHub - cesaraustralia/DynamicGrids.jl: Grid-based simulations in Julia

3 Likes

You’re right, I’ve confused argument passing with assignment. One of my students thought at first that y = x in Julia copies an array x into y because that’s how Fortran behaves.

I agree with the previous post, a straightforward Julia program is usually fairly performant. An example of a popular package with a comprehensible code is GitHub - JuliaNLSolvers/Optim.jl: Optimization functions for Julia

I recommend the books Julia High Performance and Hands-on Design Patterns and Best Practices with Julia to get into more advanced concepts like type stability, closures, function barriers, parameterized types etc.

2 Likes

This is also confusing for people coming from Fortran. Actually the equivalent of x = y in Fortran would be a element by element copy, i. e. y .= x. That because in Fortran both arrays are already allocated somewhere before (might be implicit in the declaration or explicitly using allocate).

In Fortran, every variable seems as if it was a mutable variable in Julia. Even the scalars passed to functions can be modified by the functions. That makes the syntax very simple.

2 Likes

It’s very simple.

Even if you pass the data stored in global variables as arguments to the update function each time, the code will remain simple. In addition, the code will be more readable, more efficient, and more composable with other modules.

The basic pattern is shown below.

module SomeModule

struct Board{...} board state, etc. end

function initialboard(...)
    initialize the board state, etc.
    Board(state, ...)
end

function update!(board::Board, niters)
    update the board state, niters times.
end

end

board = SomeModule.initialboard()
@gif for _ in 1:100
    SomeModule.update!(board, 100)
    plot the board state.
end

Small working example (2D Ising model)

Code
module Ising

using Random

struct Board{S, T, U, V} state::S; β::T; prob::U; rng::V end

function initialboard(; n=200, β=log(1 + √2)/2, rng=MersenneTwister())
    state = rand((Int8(-1), Int8(1)), n, n)
    prob = Tuple(exp(-2β*k) for k in -4:4)
    Board(state, β, prob, rng)
end

P(i, m) = ifelse(i == m, 1, i+1)
Q(i, m) = ifelse(i == 1, m, i-1)

function update!(board::Board, niters)
    s, prob, rng = board.state, board.prob, board.rng
    m, n = size(s)
    for _ in 1:niters, j in 1:n, i in 1:m
        sij = s[i,j]
        k = sij * (s[Q(i, m), j] + s[P(i, m), j] + s[i, Q(j, n)] + s[i, P(j, n)])
        s[i, j] = ifelse(rand(rng) < prob[k+5], -sij, sij)
    end
end

end

using Plots
default(size=(240, 240), colorbar=false, ticks=false, axis=false)

board = Ising.initialboard()
anim = @animate for _ in 1:100
    Ising.update!(board, 100)
    heatmap(board.state)
end
gif(anim, "ising.gif", fps=10)

Result:
ising

5 Likes

Hi @genkuroki , I am a little confused here. Would you please tell me what is the meaning of the semi-colon in the following function definition?

I tried to delete the semi-colon and ran the code, it seemed that nothing changes. Is this semi-colon used to imply that there could be more arguments before n=200?

Thanks a lot!

1 Like

It’s simply a matter of taste. The only difference is whether to use keyword arguments or optional arguments.

The case with semicolon (keyword arguments):

julia> f(; a = 1, b = 2.0, c = "three") = (a, b, c)

This can be used as

julia> f(b = 99.0)
(1, 99.0, "three")

The case without semicolon (optional arguments):

julia> g(a = 1, b = 2.0, c = "three") = (a, b, c)

In this case, to get the same result as above, you would need to write

julia> g(1, 99.0)
(1, 99.0, "three")
2 Likes