General
Follow


Jan

Magic strings and numbers in Matlab

Jan on 24 Jul 2013
Latest activity Reply by Jan on 9 Aug 2013

Some of Matlab's toolbox functions are affected by magic strings or magic numbers, which are strings or numbers with a deeper meaning besides the normal value. Both are considered as bad programming patters, because they provoke confusions, when the magic keys appear with the normal meaning by accident. See http://en.wikipedia.org/wiki/Anti-pattern
Example 1:
clear('myVariable')
clear('variables')
While the 1st clears the variable myVariable, the later clears all variables. Here 'variables' has a meta-meaning. The problem appears, when 'variables' is an existing variable:
a = 1;
variables = 2;
clear('variables')
disp(a) % >> 1
Only variables is cleared, which cannot be understood directly when its definition is 1000 lines before.
Example 2:
uicontrol('String', 'default')
This creates a button with the empty string '' instead of the expected 'default', because this is the magic string to invoke the default value get(0, 'DefaultUIControlString'). The same concerns properties of other graphic objects also, e.g. the 'name' property of figure or the string of uimenu. There is a workaround which allows the user to display 'default': Simply use '\default'. Unfortunately this is doubled magic, because in consequence it is impossible to display the string '\default'. Obviously a bad idea.
Example 3:
Graphic handles are doubles (although gobject of the new R2013a seems, like this is subject to changes? [EDITED: Yes, it changed with HG2 in R2014a]). But then a handle can be confused with data:
a = axes; % e.g. 0.0048828125
plot(a, 2, '+')
But you cannot draw the point [0.0048828125, 2] by this way, because the 1st input is considered as handle of the parent. Here all possible values of handles are magic. Collisions are very unlikely, but there is no way to avoid them reliably - as long as handles have the type double.
Question:
Which functions are concerned by magic values? What are the pitfalls and workarounds?
Jan
Jan on 9 Aug 2013
In sprintf and fprintf the backslash is magic in the format strings, because it is used to make the special characters magic, e.g. '\n' becomes a line break caracter char(10). If you want the backslash to appear, '\\' is required.
This well known escaping works well in many programming languages. The workaround to use \\ to display one \ appears seldom in the forums. But sometimes users did not consider this when the format string is provided as variable:
This works fine:
S = 'hello'
fprintf([S, '\n']);
This fails under Windows due to the backslashes in the path:
S = which('plot.m');
fprintf([S, '\n']);
The general solution is not to insert dynamically created strings in athe format string, but to let %s handle the escape characters:
fprintf('%s\n', S);
Equivalent effects appear in error messages:
error(['Cannot open: ', FileName]) % Prone to bad output
error('Cannot open: %s', FileName)
[EDITED] The problem of escape characters in the error message appeared in several 3rd party Matlab toolboxes, which have been developed under Linux. Then porting them to Windows requires to consider the backslashes in the path names.
Jan
Jan on 1 Aug 2013
When legend is used in the form legend('string'), the following names have magic side-effects:
'off', 'toggle', 'hide', 'show', 'boxoff', 'boxon'
A workaround is either to provide the line handle as first input or to use a cellstring to store the names:
h = line(rand(1,10));
legend(h, 'off') % 'off' appears as name
legend({'hide'}); % 'hide' appears as name
Daniel Shub
Daniel Shub on 9 Aug 2013
Now I see the magic. It appears to only be magic when it is the first string
plot([1:10; 2:11]')
legend('My Line', 'boxoff')
gives two legend entries, but
plot([1:10; 2:11]')
legend('boxoff', 'My Line')
fails to create a legend.
dpb
dpb on 9 Aug 2013
No, what that suggests is that there's an error in trying to put two labels in the legend call when there's only one line in the plot.
Try
plot(1:10)
legend('boxon')
pause(.5)
legend('1')
pause(.5)
legend('boxoff')
and see what happens...
Jan
Jan on 9 Aug 2013
I meant the calling with one string like:
legend('boxon');
You cannot use this style to apply the name 'boxon' for a single line. While 'boxon' might be too unusual to be used as a title, 'off', 'hide', or 'show' are less unlikely.
I've tested this with 6.5, R2009a and R2011b, but the general problem becomes even worse when the magic strings change with the version.
But let me repeat the definition of "magic", if this is still not clear:
Strings are magic, when they cause certain programmatic effects, while other strings in exactly the same command are treated as normal strings. Or when 'off' or 'all' are treated differently than 'qhjuast23'.
Daniel Shub
Daniel Shub on 9 Aug 2013
When you say magic side effects, what do you mean (and what version of MATLAB are you using). On R2013a
clf
plot(1:10)
legend('My Line', 'boxon')
gives me
Warning: Ignoring extra legend entries.
In legend at 286
suggesting LEGEND is treating "boxon" as any other string. I am not sure, but I think this might be a relatively new feature.
Jan
Jan on 1 Aug 2013
Although the documentation is clear and there is no magic meta meaning of a specific string, something is magic with the save command. There is a surprising large number of problems in the Matlab forums concerning the confusion of:
save filename A
save 'filename' A
save(filename, A)
save('filename', 'A')
And in the next step for the import:
load(filename)
Data = load(filename)
A = Data.A
A = load(filename, 'A')
But what could be the reason for the frequent problems? save is one of the very rare Matlab functions, which do not get the inputs as value, but as name of the variable. While this is considered as a bad programming style for eval and assignin, it is the standard for save. Modern Matlab versions accept the '-struct' flag (well done, TMW! Due to the leading '-' this is not a magic string), such that a kind of passing by value happens.
The output of load must be a struct, but when only one variable is saved, this feels like an unexpected level of abstraction.
Nevertheless, no magic here. The documentation is complete. So this remains a job for the forum.
Jan
Jan on 1 Aug 2013
From the docs of close:
close name % Close figure with the specified name
close all % Close all non hidden figures
Then 'all' is magic: Even when a figure is called 'all', close('all') closes all figures and not only the one with the matching name. This differs from the behavior of clear all, which clears the variable called all only, if it is existing.
Jan
Jan on 30 Jul 2013
This seems to become a one-man tutorial thread as in the early days of this forum.
Jan
Jan on 30 Jul 2013
Matlab strings do not use a terminator to mark the end, but the C-routines, which are called for IO operations consider char(0) as terminator:
s = char([65, 0, 66])
% >> A B
disp(s)
% >> A B
fprintf('%s\n', s)
% >> A (in 2009a)
% >> AB (in 2011b)
This is not pure magic, but a in fprintf the \0 has an additional meta meaning compared to disp.
dpb
dpb on 1 Aug 2013
Just to add more consistency to the inconsistencies... :)
Consider
>> sprintf('%s\n',['A' char(0) 'B'])
ans =
A B
>> fprintf('%s\n',['A' char(0) 'B'])
AB
>> fid=fopen('AB.txt','w');
>> fprintf(fid,'%s\n',['A' char(0) 'B']);
>> fid=fclose(fid);
>> type 'AB.txt'
A
>>
Three (slight) perturbations; three different results...the char(0) terminated the write before reaching the \n, in the file.
Erratum--the result for the file is a fignewton of type NOT fprintf -- it appears to use the C characteristic of terminating a string on \C. Reading the file w/ fgets shows that all the characters were put into the file as given. But one will have trouble reading the file just written expecting it to be a string/record.
In summary, on further exploration the only difference I can actually find is that between [s|f]printf in appearance to the console.
dpb
dpb on 31 Jul 2013
Yes, it seems to be but it's in the display portion of i/o instead of generic is the difference was pointing out.
The half-width thingie is just dependency on font being variable-pitch isn't it?
Walter Roberson
Walter Roberson on 31 Jul 2013
In the sprintf() case, 0 appears to be special-cased to show up as blank, as char(1) to char(31) show up as empty except for horizontal tab (^I, 9), vertical tab (^K, 11), linefeed (^J, 10), page feed (^L, 12), carriage-return (^M, 13); and backspace (^H, 8) has negative width. Interestingly, vertical tab and page feed show up as half-width compared to null (0)
dpb
dpb on 31 Jul 2013
Seems to be a fignewton from the display rather than just the i/o, Jan... (all in 2012b)
>> disp(['A' char(0) 'B'])
A B
>> sprintf('%s\n',['A' char(0) 'B'])
ans =
A B
>> fid=fopen('jan.txt','w');
>> fprintf(fid,'%s\n',['A' char(0) 'B']);
>> fid=fclose(fid);
>> type 'jan.txt'
A >>
Looks like anything to the screen the char(0) is preserved/passed thru; anything to a physical file is interpreted as EOR.
Jan
Jan on 30 Jul 2013
I've found a magic character in strings for the GUIs:
uicontrol('Style', 'listbox', 'Pos', [1,1,100,100], 'String', 'asd|bsd')
Then the bar is used as separator. This does not matter the style 'edit' or 'text', but the same method can be used to separate tick marks:
set(gca, ...
'XLim', [0, 40], ...
'XTick', 0:5:40, ...
'XTickLabel', '0| |10| |20| |30| |40')
[EDITED] To display bars, use a cell string instead. (Thanks, Walter)
Walter Roberson
Walter Roberson on 30 Jul 2013
If you use a cell array of strings, the bars will not be treated as special.
Jan
Jan on 25 Jul 2013
uicontrol's, figure's and uimenu's use these magic strings for properties, which are strings:
'default', 'remove', 'factory'
Such properties are e.g. 'String', 'Callback', 'Name', 'Tag', etc.
A bullet-proof workaround:
S = input('Input the button string: ', 's');
if any(strcmp(S, {'default', 'remove', 'factory'})
S = ['\', S];
elseif any(strcmp(S, {'\default', '\remove', '\factory'})
warning('String "%s" padded to support uicontrol!', S);
S = [' ', S];
end
uicontrol('String', S);
Such an ugly catching of rare exceptions is a typical effect of magic strings.
When a callback function is called 'default', use the function handle instead of a string:
uicontrol('Callback', @default);
A real solution, not only a workaround, is to strictly avoid using these names as function names or as 'String' property and never allow not controllable user-input as a property. But this can be a limitation, if e.g. the ten most frequent words of an arbitrary text file should be displayed in 10 buttons.
Kelly Kearney
Kelly Kearney on 1 Aug 2013
Same words are magic (and undocumented as such) for the legend command. And using them leads to all sorts of weird results:
str = {'bar1', 'bar2', 'line1'};
for iax = 1:4
subplot(2,2,iax);
bar(1:10, 10*rand(10,2));
hold on;
plot(1:10, 1:10, 'g');
legstr = str;
if iax > 1
legstr{iax-1} = 'default';
end
legend(legstr);
title(sprintf('legend(''%s'',''%s'',''%s'')', legstr{:}));
end
Discovered that one through experience when trying to plot model results run with default parameters along with some perturbations. Submitted a request in 2010 to add that to the documentation (since I had to contact the Mathworks to learn about the '\default' trick)... looks like it's still undocumented in R2013a.
dpb
dpb on 25 Jul 2013
@jan--you think such ruminations are worth of answer? I really hadn't considered it to quite that level...
But, I'll await your judgment and clean up as you think fit...
I will note after thinking about 1) a little more -- it's a case that could be considered
a) A bug--if a variable named 'variables' exists and the argument to clear contains 'variables' then clear also should test for that case and call itself recursively(*) (actually twice should do it); or
b) Programmer's error -- shouldn't name a variable variables. The problem there is that as above there is no definitive grammar table that gives the rules and/or the, as you call them, "magic" strings or numbers. At least in a defined language such as C or Fortran, while there are ample opportunities for programmer to make errors that aren't necessarily catchable by a compiler until runtime or by simply malfunctioning code per intent, there is a complete set of rules by which one can determine whether the code in question is or is not conforming. If, by chance, one finds a case where that isn't so, then there is a defined process by which that "hole" or anomaly or error in the Standard can be addressed and a definitive ruling made on it. AFAIK, there is no equivalent in Matlab to the defined process for the user--TMW does what TMW decides to do.
(*) Of course, this still has the problem then that there would be no way to only delete the variable variables. The root cause comes back again to the issue that one can alias anything (almost) in Matlab and that there is no rule against such name clashes enforceable except by programmer knowledge which gets more and more problematical the more features that are added.
Jan
Jan on 25 Jul 2013
@dpb: Please post this in the answer section.
dpb
dpb on 24 Jul 2013
Workarounds--
1)
clearvars % doesn't have the keyword 'variables'
Of course, since one can alias almost anything in Matlab other than a very few reserved words, one still can find ways to break it I'm sure.
2. Have to think on this one...hadn't ever come across that particular symptom in large part I suppose because I don't do gui's.
3. Always use handle first form(+), then the data that may have same value as a handle won't be referenced in a context that matters.
(+) Of course, may mean creating the empty axes() object first, then writing to the handle which is a pita to always do. Alternatively, test for a handle first and branch--equally painful to code.
All in all, these and other warts are a remnant of the more or less ad hoc design of Matlab that has simply grown from Cleve's general concept. Much like Fortran, TMW has worked to maintain as much compatibility as reasonably feasible including no typing, automagic allocation and few reserved words (Fortran has none). The effect as more and more features are added is the inevitable unless there is generated an actual formal grammar that will provide the basis for eliminating such. But, I doubt that can be done at this point w/o what are/would be considered unacceptable changes to the language.
Interesting topic, however, and at moment only the more-or-less superficial specifics of the examples is all I have time for. I'll try to think some in more depth....