classdef func
	properties (GetAccess=private, SetAccess=private)
		fn
		args
		slots
	end
	methods
		% A func is a partially applied function: Any given function can
		% have some arguments bound to some given parameters, producing a
		% new function which needs only the remaining unbound parameters to
		% be supplied. In addition, the order in which parameters supplied
		% in a subsequent invocation are bound to unbound arguments can be
		% controlled using a slot permutation mechanism. Subscripting operation
		% () is overloaded so that function application works in the normal
		% way except when there are no arguments, in which case you must use FEVAL.
		%
		% For example, if linspace is a function such that
		%
		%    linspace(a,b,n)
		%
		% returns an array of n values linearly spaced between a and b,
		% then after
		%
		%    fromzero=func(@linspace,0)
		%
		% fromzero(b,n) returns n linearly spaced values from 0 to b.
		% The arguments can be permuted, eg,
		%
		%    perm(func(@linspace),[3 1 2])
		%
		% returns a function that takes the arguments in the order (n,a,b)
		% instead of (a,b,n). Permutation and binding can be combined, eg
		%
		%    countdown=bind(perm(func(@linspace),[3 2 1]),10)
		%    countdown(5,50)
		%    ans=
		%       50 45 40 35 30 25 20 15 10  5
		%
		% See Also:
		%    PERM, BIND, CURRY, FEVAL, BINDAT
		%
		% Internal structure:
		%    cfn.fn    : an ordinary matlab function handle
		%    cfn.args  : a cell array with bound arguments filled in in the order
		%               that the ordinary function fn expects them. There may be 
		%               empty slots if the arguments have been permuted (see CFPERM)
		%    cfn.slots : List of empty slots in the order that they are intended to be
		%                bound when this curried function is evaluated or has more
		%                parameters bound. Eg, the first parameter will be bound to
		%                to the slots(1)'th argument of the eventual call to cfn.fn

		% default function is null function
		function c=func(fn,varargin)

			if nargin<1, fn=@nop; end
			if isa(fn,'func'), c=fn;
			elseif isa(fn,'struct'),
				c.fn=fn.fn;
				c.args=fn.args;
				c.slots=fn.slots;
			else
				c.fn=fn;
				c.args=varargin;
				c.slots=[];
			end
		end

		% bind - Bind arguments to a func to create partially applied function
		% 
		% bind(fn,a1,a2,..)
		%    fn is curried function returned by FUNC, BIND or PERM,
		%    the given parameters are bound to the free slots of the function
		%    and a new curried function is returned.

		% Matlab 'feature': if ANY of the paremeters
		% to bind are function objects, then this function gets called, even
		% if the first parameter is just an ordinary function pointer.
		function c=bind(c,varargin)
			if isa(c,'function_handle'), c=func(c); end

			% first, fill available slots in arg list using supplied args
			m=min(length(c.slots),length(varargin));
			c.args(c.slots(1:m))=varargin(1:m);
			c.slots=c.slots(m+1:end);                 % remove filled slots
			c.args=horzcat(c.args,varargin(m+1:end)); % append any left over args
		end

		% BINDAT: Bind arguments at specified positions to create partially applied 
		% function
		% 
		% BIND(cfn,pos,a1,a2,..)
		%    If cfn is an ordinary function, the parameters a1, a2 etc
		%    are bound at the positions specified in pos(1),pos(2), etc.
		%    Otherwise, the same as BIND.
		%
		%    See BIND

		% need to make sure pos is a complete permutation before
		% calling perm
		function c=bindat(c,pos,varargin)
			pos=[pos setdiff(1:max(pos),pos)];
			c=bind(perm(c,pos),varargin{:});
		end

		% DISPLAY - Display FUNC object as string
		function display(c), disp([tostring(c),sprintf(' :: func(%d->%d)',nargin(c),nargout(c))]); end

		% FEVAL: Evaluate partially applied function
		%
		% FEVAL(cfn,a1,a2,...)
		%     The given parameters a1, a2, etc. are bound to the arguments of cfn,
		%     and eventually to the arguments of a matlab function, possibly
		%     permuted according to cfn.slots (see CFPERM)
		function varargout=feval(c,varargin)
			% first, fill available slots in arg list using supplied args
			if ~isa(c,'func'),
				[varargout{1:max(1,nargout)}] = builtin('feval',c,varargin{:});
				return;
			%	error('This shouldn''t be happening!');
			end
			m=min(length(c.slots),length(varargin));
			args=c.args;
			args(c.slots(1:m))=varargin(1:m);

			% append any left over args
			if nargout==0, c.fn(args{:},varargin{m+1:end});
			else [varargout{1:max(1,nargout)}] = c.fn(args{:},varargin{m+1:end});
			end
		end


		% GETARG - Extract a bound argument from a FUNC object
		% getarg :: (func (D1,...,Dn)->R, k:natural) -> Dk
		function a=getarg(c,n), a=c.args{n}; end

		% GETFN - Get ordinary function handle from FUNC object
		function fn=getfn(c), fn=c.fn; end

		% PERM: permute arguments to a curried function
		% PERM(cfn,perm)
		%    cfn can be an ordinary function handle or a curried function as returned
		%    by BIND or PERM.
		%   
		%    The 1st argument of PERM(F,P) will bind to the P(1)'th argument of F.
		%    Sequential permutations compose properly.
		%    P must be a complete permutation: if length(P)=L, then P must
		%    contain each of the intergers 1 to P exactly once.
		%    The binding order of arguments after the Lth is not affected.
		function c=perm(c,slots)
			m=length(c.slots);
			n=length(slots);
			l=length(c.args);
			if m<n,     c.slots=[c.slots l+1:l+n-m]; 
			elseif m>n, slots=[slots n+1:m]; end
			c.slots=c.slots(slots);
		end

		function varargout=subsref(c,S)
		% SUBSREF - Subscript referencing for FUNC objects
		%
		% f(a,b,c)=feval(f,a,b,c) - parenthesis subsref invokes the function
		% f{a,b,c}=bind(f,a,b,c) - braces subsref binds more arguments


			s=S(1);
			switch s.type
				case '()'
					% first, fill available slots in arg list using supplied args
					m=min(length(c.slots),length(s.subs));
					args=c.args;
					args(c.slots(1:m))=s.subs(1:m);
					[varargout{1:nargout}] = c.fn(args{:},s.subs{m+1:end});
				case '{}'
					varargout{1}=bind(c,s.subs{:});
					if length(S)>1,
						[varargout{1:nargout}] = subsref(varargout{1},S(2:end));
					end
			end
		end

		function str=tostring(c)
		% TOSTRING - Implementation of string conversion for FUNC objects
		%
		% tostring :: func -> string

			str=[];
			slots=c.slots; m=length(slots);
			args=c.args;   l=length(args);
			n=max(slots);
			if l<n, args=[args cell(1,n-l)]; l=n; end;
			for i=1:m
				args{slots(i)}=['#' num2str(i)];
			end

			for i=1:l
				str=[str tostring(args{i}) ','];
			end
			if n>0, str=[str ' ']; end
			if isa(c.fn,'function_handle') fstr=['@' func2str(c.fn)];
			elseif isa(c.fn,'inline') fstr=['\' char(c.fn) '\'];
			end
			str=[ fstr '(' str '...)'];
		end
		function n=nargout(f), n=nargout(f.fn); end
		function n=nargin(f), n=nargin(f.fn)-length(f.args); end
	end
end

