Programming style and file-scope code

Private: Q&AProgramming style and file-scope code
sdonn asked 8 years ago

So, in Quasar we can do something like this

[a,b] = load("matrices.qdat")
function [] = print_info()
    print mean(a)
    print mean(b)
end
function [] = main()
    print_info()
end

In my particular use-case, the file-scope code is actually the parsing of a text file containing all imaging variables for BAHAMAS – file names, platform heights, plant heights, … I only wish the code to be called once, at file load (aiming for something like the java static code blocks).
The questions:

  • Am I correct in thinking that this code will only be called once, on the first import? In this case, it’s not particularily bad that it would be called more than once — except that it is inefficient to do so.
  • is this the best way to go about this? Or is there an elegant alternative that I missed? This way, I am cluttering file-scope with a lot of variables, but if I house this in a function I need weird tricks to be able to change file-scope variables (because I do not know the number of elements in advance, i.e. defining a:cube and then setting a=randn(4) in a function only changes the function-scope pointer).
4 Answers
bgoossen answered 8 years ago

Question 1: yes indeed, the code will only be called once, on the first import
Question 2: the use of file-scope variables are intended to be a “rapid prototyping” feature. In combination with “load”, use them for variables that will not change throughout the program (for example: lookup tables, calibration matrices, color conversion matrices, …).
Scalar variables defined at the file-scope, as well as matrix/object references cannot be modified from within functions.
Alternatives: define functions that load the data that either return multiple parameters, or objects with the particular values initialized.
For example, I am often using functions to load custom datasets:

function fangeom = load_dataset(name)
    match name with
    | "set1" ->
        fangeom = object()
        fangeom.distsrcdet = 291.20 % distance source-detector (in mm)
        fangeom.distsrcobj = 115.84 % distance source-object (in mm)
        fangeom.voxelpitch = 0.075
        fangeom.detoffset = 24.27 % pixels
        fangeom.dimx = 1184 % number of detector elements
        fangeom.pixelpitch = 0.1 % distance between the detector elements
        fangeom.numangles = 1280 % number of detector angles
        fangeom.airraw_value = 3291 % for Beers-Lambert law

         f = fopen(strcat(path, angle_file), "rb")
        fangeom.angles = degrees_to_radians(fread(f, fangeom.numangles, "float32"))
        fclose(f)
    end
end

This allows more easily to plug in other datasets, rather than having to comment out code on the file scope level. The dataset name can then optionally be specified through the command-line:

function [] = main(dataset_name = "set1")
    fangeam = load_dataset(dataset_name)
end
bgoossen answered 8 years ago

The general convention is hence to avoid global scope variables, unless for rapid prototyping purposes.
Global variables are bad.

sdonn answered 8 years ago

Yes — I have similar data adapters for a project.

Really Bad Reasons to use Globals:
     "I don't want to pass it around all the time." 

… oops 🙂
I would say that the quasar ‘globals’ are more of “hidden globals” as mentioned in that link: they are only accessible from the same file, right ?

bgoossen answered 8 years ago

Not exactly. When a module is imported, its global variables are made accessible to all code that is executed next.
Note that due to First class functions, all functions at the global scope of a .q module are globals.
To reduce naming collisions, in the future it might be better that globals are only visible one level in the dependency chain (e.g. when A->B->C, A can see the globals of only B but not C), and idem for reductions (#220). But probably I won’t have any time soon to implement this feature.