MATLAB Answers

Kouichi
1

A way to work around MATLAB's inability of put a local function into M-script

Asked by Kouichi
on 6 Jul 2016
Latest activity Commented on by Kouichi
on 15 Sep 2016
I have been wondering why we are not allowed to put a local function in a script file. According to related threads below, it seems that I am not the only one.
Down side of creating functions that are only to be used for a specific script might be...
  1. Names: If you keep doing this, your folder will be cluttered with lots of one-shot functions, and it is highly likely that you end up having lots of them with very similar and confusing names. Once you loose the track of record, you might not able to work out which function is right for you.
  2. Scope: Closely related to the above. Functions that are put in a folder are within the scope of all other files in the folder, or if the folder is in the MATLAB search path, of all the MATLAB files. Frequently used utility functions and those one-shot functions will be mixed up. Finding a right function may become more difficult with lots of other candidates with confusing names. Ideally, your one-shot functions should be only visible to the script that they were dedicated to.
  3. Maintenance: If you have to prepare five separate function .m files for one script, it would be more prone to loose their integrity, when copied to other place or people, for example. One-shot functions should better be kept in one place for maintenance.
I've been thinking about this, and came up with a very basic approach that can work around most of the above issues, albeit not entirely.
1. Use date-based sequential stem name for script file. A name like "scr2016_07_05_200000_xxxxx.m" may be beneficial, in that all the script and related files (html, mlx, pdf) will be shown in date order. It is easy to relate these sibling files. Also, it is useful to remember what you were up to on a specific day. Sequential numbering is a great way to avoid the pain of thinking of a unique name for just simple scripts.
2. Deploy a utility class with static methods. Create a classdef file with the name "scr2016_07_05_200000_xxxxx_util.m" Leave properties empty and put an attribute (Static) to methods as below. And then, you can add as many static functions as you want here.
classdef scr2016_07_05_200000_xxxxx_util
%scr2016_07_05_200000_xxxxx_util
properties
end
methods (Static)
function out1 = func1(var1, var2)
end
function out2 = func2(var3, varargin)
end
end
end
3. In your script, call an instance of "scr2016_07_05_200000_xxxxx_util" as below (Note you don't have to create an object to use Static methods. classname.staticMethodName(args,...) works. Below is just to avoid repeating lengthy class names.):
u = scr2016_07_05_200000_xxxxx_util;
4. Then, you can access to the static functions like this.
C = u.func1(A,B)
Benefit of this approach so far may be...
  1. Scope of those static methods are limited.
  2. You can find all of the related function in one place.
  3. You don't have to worry about thinking of absolutely unique function names every time. You only have to worry about uniqueness within the class, which is pretty easy.
  4. In essence, your script will have a twin brother of a dedicated utility class. I found that working on a .m or .mlx script and the classdef .m file side by side in one screen was extremely efficient. Your script will show abstract code on the left, and detailed workings in static methods in the classdef file on the right.
I'd like to hear your even better ideas or constructive feedback.
Thank you for reading.
Best, Kouichi

  7 Comments

Well... even that top level script can be a function without arguments that calls all you need. I don't understand the issue.
I am not sure I agree with you regarding the suitability of messy code.
So the issue is now about function VS script, the classic battle huh? ;)
I tried that way, i.e. putting everything into a function main() without an input argument. When a lengthy computation is involved, and an error occurs after that, the result of the computation will be lost. You have to start from the beginning all over. You can automatically save intermediate results into mat files to restart from there within the function. But then you would have to load that mat file into that function's Workspace.
I thought it was more tedious in comparison with using Base Workspace for a script. Because the code can't tell if I want to recompute it or to load a mat file, the process of loading needs to be done interactively, probably by using input() and questdlg() to make a choice, or keyboard() to enter debug mode. Every time you use dbquit() to leave from debug mode, however, you'll have to do it again.
If you don't have a problem in restarting from the middle of function file, then would you be pleased to let me know how you do it?
In essence my top level code often ends up like this, deliberately keep it as a script.
% main.m script
param1 = 1;
param2 = 'a';
out1 = func1(param1,param2); % lots of jobs
save('out1') % for recovery
param3 = 1;
param4 = 'a';
out2 = func2(param3,param4); % lots of jobs
save('out2')
...
I don't know, I might be totally wrong, but in my current understanding, using functions are like diving into the sea. You take a big breath and you go down, hoping there aren't lots of bugs. Ouch, there's a bug. MATLAB stops there ( dbstop if error). You do a bug fix a bit, and you go back to the sea surface to take a deep breath. Then you go for another dive. It's rather difficult to bring something back to the sea surface in case there is a bug, and everything you do underwater may be lost.
Besides, why do you think they did bother to create Live Script? Just to let us pay more money?
I think Live Script is really suitable for "I just wanna have a look"-kind of situations. It's a messy, flat world, like a sandpit, but you can easily go back and forth to see the effect of tiny changes you make in parameters because variables will stay there.
For example if want to see hundreds of slightly different figures, it is often more intuitive to keep the figures embedded right next to the code that produced them by using publish() or LiveScript. Loads of floating MATLAB figures produced by functions will be difficult to work with.
So when I want to create a tool or build an infrastructure, I'll definitely go for functions and classes. When I want to analyse data, create plots and do statistics, however, it's a different purpose and I opt for scripts or new Live Scripts for top level code. If you know a better way, please let me know.
This is deviating quite a lot from the original question, but anyway the way I usually cope with this sort of top level function/script that may need restarting in case of issues/crash/abort is to actually use a class. This has several advantages:
All the default constants that you would define in your scripts, they can just be the default values of the properties of your class.
If the user wants to modify some of the constants. Easy, just change the given properties.
You can save the object to a mat file, so that you have a record of the settings used without needing to modify the source code. Good for version control. You need to reprocess the data with whatever settings you used a year ago. Reload the object from the mat file.
You can have a method that starts the processing from the beginning and a similar method that resumes from where it left off.
E.g.:
classdef DropSizer < handle
properties (Access = public)
SizeThreshold = 20;
EdgeWidth = 10;
%...
end
methods
function Run(this, imagelist)
state = struct('ImageList', imagelist, 'CurrentIndex', 0);
this.Process(state);
end
function Resume(this, resumestate)
this.Process(resumestate);
end
end
methods (Access = Private)
function Process(this, state)
%processing may take a long time and crash
while state.CurrentIndex < numel(state.ImageList)
currentimage = state.ImageList(state.CurrentIndex + 1);
%entering procedure that may error
try
%do something
catch exception
fprintf(2, '<strong>While processing image index %u</strong>:\n', state.CurrentIndex + 1);
fprintf(2, '%s\n', exception.message);
assignin('base', 'resumestate', state); %store in base workspace or you could save to mat
return;
end
state.CurrentIndex = state.CurrentIndex + 1;
end
end
end
end
My functions are actually function objects which I find a lot neater when you want to deal with optional inputs (they're properties with default instead).

Sign in to comment.

Tags

3 Answers

Answer by Friedrich
on 7 Jul 2016
 Accepted Answer

Hi,
how about giving 16b a shot? Just do it ;)

  3 Comments

Hahaha, I've been trying out R2016b for weeks and it never occurred to me that they might have changed their policy about script!!! Strictly speaking I can't tell you about this (it's a secret), but basically R2016b answers to this question. Thanks a lot for letting me know.
I would have thought the first thing you'd do on downloading the pre-release is to look at the release note to see what's new.
It's the first entry under Language and Programming.

Sign in to comment.


Answer by Steven Lord
on 15 Sep 2016

Release R2016b is now officially available. As listed in the first item in the Language and Programming section of the Release Notes for MATLAB in that release, you can now define local functions in script files.

  1 Comment

Thanks, I've been waiting for its arrival.

Sign in to comment.


Answer by Kouichi
on 15 Aug 2016

Actually, after trying out R2016b, which allows us to use local functions in scripts or Live scripts, I still think the alternative method I proposed above has a couple of advantages over local functions.
Especially when working with Live Editor, the use of local function is rather inconvenient because Live Editor does not allow you to use graphical debug tools (break points etc). Because they are local functions, you can not access to those local functions from Command Window, either. So basically you have no way to debug the local function graphically in a Live Script.

  0 Comments

Sign in to comment.