octaveprivatecode-organization

Access private functions from private functions in Octave [BUG]


In Matlab private functions are those stored in subdirectories with the name private. They have a restriction of visibility that: "you cannot call the private function from the command line or from functions outside the parent of the private folder" (https://se.mathworks.com/help/matlab/matlab_prog/private-functions.html).

EDIT: Octave tries to follow this: "if the path to func1 is directory/func1.m, and if func2 is found in the directory directory/private/func2.m, then func2 is only available for use of the functions, like func1, that are found in directory." (https://octave.org/doc/v6.2.0/Private-Functions.html)

I'm using Octave 6.2.

I have a couple of functions that I want to make private, say A and B. They have some common part, so it is natural to separate out this part and create another function, say C, to be called from interiors of A and B.

I placed C in the same private directory as A and B, and it seems that this construction is not allowed by the language, as I get an error saying that C is undefined. I find it counterintuitive, nonetheless, this could be expected according to the above quote from documentation of Octave, saying that private functions are visible only from the parent directory.

I tried placing a nested private subdirectory within the first one (with the relative path "private/private") as a workaround, but it doesn't solve the problem, and I still get an error with undefined C.

Thus, the only way to call C from private A and B seems to be to make it publicly visible, even though from the code architecture point of view C should not be public, since it is called only by private functions. Another way would be to keep copies of the same code of C within both A and B - with all bad consequences of the violation of the DRY principle.

EDIT: As indicated in the comments this problem seems to be Octave specific. In Matlab you can access private functions from other private functions.

My question is: What is the proper way of maintaining common parts of private functions in Octave?

EDIT2: The problem indeed seems to be a bug in the current version of Octave. The MWE provided by carandraug works fine unless...

I found that the following sequence is a mininal-not working-example making the bug explicit: 1) A.m and C.m are in the directory, A.m calls B.m. B.m is in directory/private, initially NOT calling C.m. Evaluation of A works fine. 2) Modify B.m to call C. Evaluation of A still works fine. 3) Move C.m to private. Evaluation of A returns error with C undefined.

On the other hand the following sequence (the one occurring in most situations) doesn't reveal the error: 1') A.m and C.m in directory, A.m calls B.m. B.m is in private, this time calling C.m Evaluation of A works fine. 3') Move C.m to private. Evaluation of A still works fine.

So, step 2) for some reason is crutial for the bug to occur.

EDIT3: For fresh Octave from GUI this is how it works for me (on Windows 10 Pro 20H2, maybe this matters):

>> cd C:\Octave\test\
>> ls
 Volume in drive C is Windows
 Volume Serial Number is XXXX-XXXX

 Directory of C:\Octave\test

[.]       [..]      A.m       C.m       [private]
               2 File(s)            101 bytes
               3 Dir(s)  129 967 509 504 bytes free
>> A
this is A
this is B
>> % now edit B to call C
>> A
this is A
this is B
this is C
>> movefile("C.m", "private/C.m")
ans = 1
>> A
this is A
this is B
error: 'C' undefined near line 3, column 3
error: called from
    B at line 3 column 3
    A at line 3 column 3

Solution

  • What you suggest should work in Octave so I believe your error is elsewhere. Can you provide a minimal example of how it is not working? Here's mine working in Octave:

    $ cat dd/A.m 
    function A ()
      disp('this is A')
      B()
    endfunction
    
    $ cat dd/private/B.m 
    function B ()
      disp('this is B')
      C()
    endfunction
    
    $ cat dd/private/C.m
    function C ()
      disp('this is C')
    endfunction
    
    $ octave --path 'dd' --eval 'A()'
    this is A
    this is B
    this is C