Best practices for const used as globals

so I am about to FINALLY write my first julia code after 1 year of deliberation. I decided to check out the workflow guidance in julia workflow guidance and have a few basic questions.

1 I want to have a set of linux file paths that are associated with configuration, data source and data sink. An example would be:

const data_sources = “/home/user/data_sources/”

Reading the docs it seems that putting these in a Tmp.jl ( see docs) and including it in the Tst.jl would be the way to do this. I want them to be globals. Does this sound the most prudent way to do this?

2 checking for main

I am coming from python and always put if name == ‘main’ : in my main calling script as it gives me comfort :slight_smile:

I was going to do the same in my julia script using if abspath(PROGRAM_FILE) == @__FILE__ is true . , does that seem like the best way to achieve the same thing?

thank you.

1 Like

I’d suggest an alternative over a grab bag of globals — even if they’re consts: Use a simple config file format (like TOML). Load the config as a single config = TOML.parsefile("config.toml"), pass that config to all your functions.

Julia doesn’t generally use the same sort of double-duty idiom that allows single flat-files to sometimes act like libraries and sometimes like scripts, which is the reason-for-being for name == main. I generally find it easier to just use a real package for a package.

6 Likes

Hi @mbauman

thank you for replying. I think you are confusing me with someone with a brain :slight_smile: I have a scant knowledge of how a TOML file is used but nothing useful. Could you point me at an example of this approach please?

I learn faster from examples.

ALSO I don’t really understand this I generally find it easier to just use a real package for a package. remember I have no brain

1 Like

Julia has a built-in package for handling TOML files (a convenient, user-readable configuration file format)
Say you have a file config.toml with contents

data_sources = "/home/user/data_sources/"
data_sinks = "/home/user/data_sinks/"

You can then load the config file at the beginning of your code:

using TOML
config = TOML.parsefile("config.toml")

which gives

julia> config
Dict{String, Any} with 2 entries:
  "data_sinks"   => "home/user/data_sinks/"
  "data_sources" => "home/user/data_sources/"

Any function that needs the configuration data can take config as an argument instead of using global constants.

Regarding “using real packages”: Julia’s tooling mostly assumes you’re writing code within a Julia project or package, which makes it much easier to handle versioning, dependencies, testing, etc.

Chris Rackauckas has a popular video tutorial on package development here: Developing Julia Packages - YouTube

7 Likes

@stillyslalom perfect explanation and thank you so much for giving me such a wonderful example. Not even I could misunderstand that. OUTSTANDING.

Thank you for the link and I’ll certainly watch it but it seems that Chris is using vstudio and I have decided to use Pluto.jl. Also Pluto has it’s own Pkg system that I find very attractive. here’s the video I watched

Pluto.jl one year on

So my development “approach” is to noodle away on a large ( 20 by 20") piece of paper using propelling pencil to get to a form of drawn pseudo-code ( NOT flowchart more state machine) then I form conceptual boxes which leads to functions. Fons et al are striving to get to the onscreen version of that and I think they are going to do an excellent job. Phillip the Corgi’s ta ( Prof Edelman) :slight_smile: seems to be on board with it Prof Edelman and phillip the corgi on Pluto

Again thank you so much for clearing this up for me

This suggests a pattern of invoking a Julia program from the command line and running it like a script, which is not a great development experience nor does it allow you to take advantage of precompilation. It’s probably better to start the Julia REPL and call a function to run a program. You can then modify the function in the REPL or use Revise.jl to reload your code file.

If I want a driver for my program, I usually make a special module for that case and invoke the code from the __init__ function, which usually just calls another function.

For example, I might make a HelloWorld.jl with the following contents:

module HelloWorld
    export helloworld
    helloworld() = println("Hello World")
    __init__() = helloworld()
end

I can then execute it as follows:

$ julia HelloWorld.jl
Hello World

I can also work with in the REPL as follows:

julia> module HelloWorld
           export helloworld
           helloworld() = println("Hello World")
           __init__() = helloworld()
       end
Hello World
Main.HelloWorld

julia> HelloWorld.helloworld()
Hello World

julia> using .HelloWorld

julia> helloworld()
Hello World

To start hacking on it, I might then do

julia> import .HelloWorld: helloworld

julia> helloworld() = "Hello Universe"
helloworld (generic function with 1 method)

julia> helloworld()
"Hello Universe"

julia> HelloWorld.helloworld()
"Hello Universe"

If I wanted to continue to edit the code in the file, I might then use Revise.includet("HelloWorld.jl") in the REPL. I would then continue to modify the helloworld() function until I met my basic prototyping threshold.

Eventually I would want to start formatting the code into a package. That usually would mean moving the file into ~/.julia/dev/HelloWorld/src/HelloWorld.jl and creating ~/.julia/dev/HelloWorld/Project.toml to give the module a unique UUID. PkgTemplates.jl can help you do this. You can then invoke your program via using HelloWorld within the REPL or julia -e "using HelloWorld" from any directory. You might complain about wanting your code to live somewhere else, and then we would need to go into details of the JULIA_DEPOT_PATH environmental variable or using symlinks. It’s usually not worth it.

I know Julia looks like a scripting language, and it’s tempting to borrow workflow from your previous experience from scripting languages. However, Julia is really a compiled language, so that requires some changes in behavior otherwise you will spend a lot of time needlessly recompiling code while you are trying to work on it. That’s a formula for making everything slow. The easiest way to develop is to use the REPL which helps cache compilation for you. Eventually though we need form packages with unique identifiers so that Julia has some kind of compliable unit to cache.

2 Likes

@mkitti WOW!!! thanks for the great write up and examples. Certainly food for thought there. I DID forget that julia is compiled and not just a simple interpreted scipt. In my defense :slight_smile: I got the code

abspath(PROGRAM_FILE) == @__FILE__ is true.

from the official julia docs, look under heading Scripting.

how to figure out main

1 Like

Using that to figure out when to do command line argument parsing sounds reasonable, although I would likely still use it from __init__.

to be honest I think you were right, I was being too pythony in my thinking.

I was going to create a start.jl module which set the constants for file locations, set parameters for channels, maybe allocate threads to particular cores, allocate array memory space and then start calling functions which dictated the changing system by examining channels et al.

I also want to use Pluto.jl as my ide and not vscode or emacs. I know the benefits of both but I want to have some fun in this new world and Pluto.jl seems fun. If I take this course I want to make sure that I am taking full advantage of Pluto.jl and it has it’s own Pkg approach that seems attractive to me.

1 Like