Matlab 的闭包

==== file: newCounter.m ====
function [incre] = newCounter(init)
    val = init;
    incre = @counter;
    function result = counter()
        val = val + 1;
        result = val;
    end
end

==== command lines ====
>> f = newCounter(0); 
>> f() 
>> ans =
    1

>> f() 
>> ans =
    2

>> f() 
>> ans =
    3

>> fInfo = functions(f); 
>> fInfo.workspace{1} 
>> ans =
    包含以下字段的 struct:
        init: 0
        incre: @newCounter/counter
        val: 3

我写matlab习惯写一堆匿名函数,一来是因为在运行脚本里不给定义函数,二来可以降低代码复写率,提高效率。虽然看起来还是挺丑的。前几天突发奇想,想看下matlab的闭包机制。由于之前写JS和Python,一上来理所当然的写出了下面的代码:

f = @(a) @(x) x + a;
fCell = cell(10,1);
for i = 1 : 10
fCell{i} = f(i);
end
==== command lines ====
>> fCell{1}(5) 
>>     6
>> fCell{6}(5) 
>>     11

这样看是没什么问题,但是像下面这样写也是没问题的:

fCell = cell(10,1);
for i = 1 : 10
    fCell{i} = @(x) x + i;
end
==== command lines ====
>> fCell{1}(5) 
>>     6
>> fCell{6}(5) 
>>     11

第二种是完全没有问题的,因为在封装这个匿名函数的时候,会将函数表达式内用到的外部变量封进一个workspace里,看下下面这个例子,以及函数信息:

i = 2;
j = 3;
f = @(x) x * i + j;
f(10)
i = 4;
f(10)
==== command lines ====
>> ans =
    23

>> ans =
    23

>> fInfo = functions(f) 
>> fInfo =
    包含以下字段的 struct:
    function: '@(x)x*i+j'
    type: 'anonymous'
    file: 'C:\Users\nizek\Desktop\matlab PRO\Untitled.m'
    workspace: {[1×1 struct]}
    within_file_path: 'Untitled'

>> fInfo.workspace{1} 
>> ans =
    包含以下字段的 struct:
        i: 2
        j: 3

可以看到,在封装完函数之后,对外部值的改变并不会改变该匿名函数内部变量的值。而且也没办法改变(对自定义函数有办法改,见下面)。
下面用函数封装一个计数器,每次调用都会加一:

如果中途想改这个值了怎么办,可以做个hook传个函数句柄出来:

==== file: newCounter.m ====
function [incre, setter] = newCounter(init)
    val = init;
    %clear init;
    incre = @counter;
    setter = @setVal;
    function result = counter()
        val = val + 1;
        result = val;
    end
    function [] = setVal(n)
        val = n;
    end
end

==== command lines ====
>> [f, setter] = newCounter(0); 
>> f() 
>> ans =
    1

>> f() 
>> ans =
    2
>> f() 
>> ans =
    3
>> setter(100) 
>> f() 
>> ans =
    101

当然这样看起来太丑了,美化一下:

==== file: newCounter.m ====
function [clss] = newCounter(varargin)
    try val = varargin{1}; catch val = 0; end
    %clear varargin;
    clss = struct('incre',@counter,'setter',@setVal);
    function result = counter()
        val = val + 1;
        result = val;
    end
    function [] = setVal(n)
        val = n;
    end
end

==== command lines ====
>> a=newCounter(); 
>> a.incre() 
>> ans =
    1

>> a.incre() 
>> ans =
    2

>> a.setter(10) 
>> a.incre() 
>> ans =
    11

>> a.incre() 
>> ans =
    12

效率:
同样的环境下,创建10w个:
带struct的函数计数器: 3.97秒
不带struct的函数计数器: 2.45秒
封装的计数器类: 0.70秒
但是用起来倒是速度一样。

#总结
matlab里变量的作用域还是很严格的,封装匿名函数时直接把相关变量一起打包隔离workspace当你用匿名函数生成匿名函数时,甚至会隔多一层空的workspace。
当然如果实际中用的大项目计算量大的话,当然还是写一个class的运行效率高,不建议用写函数的方法来封装,而且抛弃struct的使用,速率也会更快。