How to determine if a function was called followed by a semicolon (";")?
Asked Answered
S

5

5

In a Matlab script I call a user-defined function (m-function). My function returns a value while printing the value to the command window using disp and/or fprintf calls.

When writing an expression or a statement, one puts ; at its end to suppress printing. When the expression calls my function, the ; can suppress the printing of the returned value. However, this does not effect the disp output from within the function called.

I want to eliminate the display output of the function when appropriate. Is there a way to determine whether a function call was made in an expression ending with ;?

Soll answered 20/11, 2014 at 20:32 Comment(8)
I found a partial workaround for this - the use of the evalc function for calling my function. I will elaborate on that later. Still this method does not answer the question.Soll
What sort of internal output is your function doing? Is this just to implement the display of the return values? Or is it more like logging or debugging? Can you give us a specific example? In particular, what is being displayed that isn't contained in the value returned from the function?Edrei
I think adding an extra input argument to the function that determines what is displayed is by far the simplest option here, or save all the display output as another function output so you can access it after running the function if you need it.Grubman
@AndrewJanke - in this particular case, it is merely a formatted output of the returned values. However, it does not matter for the essence of the question.Soll
@Grubman - saving the display output is basically what evalc() is doing. Yes, an extra argument is easiest, provided there is no straight forward way to tell (similar in concept to, say, nargin/nargout).Soll
@Grubman - also, it may become a problem if there are many instances of this function in an expression, or in many expressions, and thus adding the argument clutters the code.Soll
@Soll Adding an extra input/output argument (especially an optional logical true/false input) should not make much difference to your code, and should be fairly easy to maintain and understand for someone else looking at the code.Grubman
If separating error messages into the cases of verbose vs lean is what you wish to achieve, you may wish to read this questionLedda
I
4

I like the spirit of what you're trying to do, but I think that it probably goes against the common programming patterns in Matlab. As you correctly state, the purpose of the terminating semicolon is to supress printing of returned values. Trying to get it to incorporate your other feature might well require some deep hacking and ugly hard-to-maintain code. The standard way to implement what you describe is via property name-value pair arguments. For example, Matlab's optimization suite has a property called 'Display' that can be set to various values to indicate the desired level of verbosity (see optimset).

If you want to try looking for a way to check for terminating semicolons, you might look into the undocumented mint, mlintmex, and mtree functions – read more here. Unfortunately, using mlint to simply check for the "Terminate statement with semicolon to suppress output" warning (see this function on the MatlabCental File Exchange) won't work in all cases as a function call by itself doesn't produce this warning.

Update

Here's an attempt at code that could be inserted into a called function to determine if the line of the caller is terminated by a semicolon. You should be aware that this has not been thoroughly tested and is likely very fragile. I have not tested it with sub-functions or anonymous functions and I know that it fails if you wrap a line over multiple lines using ....

st = dbstack('-completenames');  % M-file of caller and line number
caller = st(2);
str = mlint('-lex',caller.file); % Struct containing parsed data

isSemicolon = false; % Assume no semicolon
for i = 1:length(str)
    % Find end-of-line corresponding to function call
    c = strsplit(str(i).message,'/');
    if str2double(c{1}) == caller.line && strcmp(c{2}(end-5:end-1),'<EOL>')
        % Check for comments
        j = i-1;
        if ~isempty(strfind(str(j).message,'%'))
            j = j-1;
        end
        % Check for semicolon
        if strcmp(str(j).message(end-2),';')
            isSemicolon = true; % Semicolon found
            break;
        end
    end
end

In other words, play around with this to learn, but I would not recommend actually using it.

Insusceptible answered 20/11, 2014 at 21:15 Comment(8)
Thanks. First, see my comment to the question. Now, I am not necessarily looking for a hack (strictly speaking) but rather a standard way of doing that. Thus, it will not go against the programming patterns. As for the mlint implementation, from a quick glance at the provided link, the mlint tool works on the m-file, parsing its content. My problem is doing it from within the file, in run-time. Regarding the use of property - I'll have to investigate that idea.Soll
You can use any of those tools at runtime. I've done so myself for things more complex than this. The point of my answer is that the property name-value pair pattern is a standard way of doing this. Approaches using evalc or parsing the code would be extremely nonstandard hacks, but they would probably be the only way to actually detect the presence or absence of a semicolon.Insusceptible
How can invoking mlint from inside my function, can tell whether the caller script has a semicolon at that calling line?Soll
If the property needs to be set every time the function is called, then I may just as easily use a single argument and check its existence/value upon entering the function.Soll
It can. It will be a lot of work on your part to extract the info from the raw data mlint gives you. Try inserting something like st = dbstack('-completenames'); mlintmex('-lex',st(2).file); in an M-file function called by another M-file. Other options beside '-lex' may be better, I don't know. dbstack returns the line number of the call in case their are multiple instances.Insusceptible
@horchler: When you say "...but I would recommend actually using it", do you maybe mean "...but I would recommend DEFINITELY NOT EVER actually using it"?Edrei
@AndrewJanke: Typing too fast. Yes, a "not" was definitely missing. Thanks.Insusceptible
@Insusceptible - thanks for the update. There's definitely some ideas to research here. Obviously at this point the effort required is just too great to justify walking this path, and the consensus is to add an optional argument of some sort.Soll
T
3

I'm afraid that the answer to your question is no. That information is simply not passed on to the function being called.

You shouldn't think about adding the semicolon as a means to "suppress printing", but rather that the lack of a semicolon instructs MATLAB to call the display function on the output variables of the function call. In other words, MATLAB interprets this code:

y = myFunc(x)

as:

y = myFunc(x);
display(y);

I think adding a 'print' or 'verbose' parameter to your function is your best bet for achieving what you want.

Transcript answered 20/11, 2014 at 23:2 Comment(0)
G
1

I think the simplest method to achieve the results you want (i.e. whether or not disp's get displayed) is to add an extra function input or output. For example, adding an input (optional, you can set default behaviour):

function y=myFunc(a,displayResults)
if nargin==1
    displayResults=true; %// set the default behaviour
end

%// if you want to print something
if displayResults
    disp(a)
end
end

Or an extra output. In this case foo produces no output to the screen, but all the messages are saved into a cell array, which can be accessed if desired:

function [x,m] = foo(a)
m={}; %// initialise cell array of output messages

x=a;

m{length(m)+1}=a; %// some message
m{length(m)+1}='another message'; %// another message
end

I think the first option will be better, the second will not deal with fprintf well, and displaying elements of m could be tricky depending on what it contains. The first method is very simple, and does not even require you to change existing code, as you can make the displayResults input optional and set the default to be what you want.

Grubman answered 20/11, 2014 at 23:39 Comment(0)
R
1

You may suppress disp outputs by locally redefining it at the beginning of the function:

function [] = hideDisplay()
%[
    % Override `disp` behavior
    disp = @(x)doNothing;

    % Next `disp` calls will no longer appear in matlab console
    disp('Hello')
    disp('World!')
%]
end

%% ---
function [] = doNothing()
%[
%]
end
Randolph answered 21/11, 2014 at 12:16 Comment(5)
But, how can this be controlled form outside the function?Soll
A solution is to place a fake disp.m file (that does nothing) in the same folder of the function you want to control (or private folder). Because of precedence preferences Matlab will then call this disp.m function instead of the general one.Randolph
I think that you may be missing the point - I want to control the output from the calling script, based on the ;. This means that, in the same script, I can have calls w/ prints and calls w/o prints.Soll
Hummm ... indeed that was not so clear ... Seemed to me that you'd almost solved everything with evalc expect for disp calls ... If indeed the case, possible workaround is to create fake 'disp.m' before to call function when you want to disable display ... and destroy this disp.m file when willling to have normal display. Anyway good luck if still not getting it !Randolph
NB: I don't understand either why to do this display control fully outside of called function (unless this is code you cannot modify).Randolph
S
0

Here's a possible workaround (to be clear - this is not a real answer to the question per-se, just a way to avoid the unwanted behaviour). Say my function is:

function y = prt_test(x)
    y = x + 1;
    disp('IN PRT_TEST')
end

Calling:

>> % Regular use - message and output are displayed:
>> y = prt_test(1)
IN PRT_TEST
y =
     2

>> % Regular use w/ ";" - only message is displayed:
>> y = prt_test(2);
IN PRT_TEST

>> % Use "evalc()" - message and output are displayed:
>> evalc('y = prt_test(3)')
ans =
IN PRT_TEST
y =
     4

>> % Use "evalc()" w/ func ";" - only message is displayed:
>> evalc('y = prt_test(4);')
ans =
IN PRT_TEST

>> % Use "evalc();" - no output:
>> evalc('y = prt_test(5)');

>> % Use "evalc();" w/ func ";" - no output:
>> evalc('y = prt_test(6);');

>>
Soll answered 20/11, 2014 at 21:52 Comment(3)
I must say that this is terrible way to code in any language. You're also likely to get downvotes just for using eval. Read this and this. And thisInsusceptible
@Insusceptible - downvoting w/o suggesting a better way tells more about the downvoter himself than the code... I did not say it is clean, but it is the only way I could find (w/o the obvious adding arguments).Soll
@Insusceptible - really, adding a "print" parameter is straight forward and easy. However it does not answer the question. The question is not asking for a solution to the print problem, but an answer to how to determine the situation.Soll

© 2022 - 2024 — McMap. All rights reserved.