[fpc-devel] Functors
Blaise at blaise.ru
Blaise at blaise.ru
Sun Dec 26 02:16:20 CET 2021
I propose that the support for https://en.wikipedia.org/wiki/Function_object be added to the FPC.
A subset of such functionality already existed as a part of my implementation of closures, so I extended that part to implement the core feature for allowing functors -- overloading of the call operator: when round brackets are applied to an instance of a record, object/class, or interface type, they are translated into a call to the method Invoke of that instance. The attached proof-of-concept functors-1.patch allows the following test case to be compiled:
-------8<-------
type I = interface
procedure Invoke;
end;
type C = class(TInterfacedObject, I)
class function Invoke(const N: Integer): Integer; overload;
procedure Invoke; overload;
end;
class function C.Invoke(const N: Integer): Integer;
begin
result := N + 9
end;
procedure C.Invoke;
begin
writeln(ClassName, '.Invoke')
end;
type H = class helper for C
procedure Invoke(const S: string); overload;
end;
procedure H.Invoke(const S: string);
begin
writeln('H.Invoke("', S, '")')
end;
var aC: C;
var anI: I;
begin
aC := C.Create;
writeln( aC(33) );
aC('hello');
anI := aC;
anI()
end.
-------8<-------
Important design points:
1) Applying round brackets to instances does not collide with the existing syntax;
2) Naturally, helpers are able to turn helpees into functors;
3) Operator () cannot be applied to types -- that would clash with explicit type conversions;
4) Explicit empty argument lists are required -- unorthogonal to routines and procedural variables, but clarity must win here;
5) {$modeswitch Closures} is required (modeswitch_closures.patch from https://lists.freepascal.org/pipermail/fpc-devel/2021-December/044261.html) -- functors are closure-adjacent in the area of functional programming.
The parts that are currently missing:
1) Implicit conversion from functors to method pointers -- should be fairly trivial to implement;
2) Support for generics -- should be straightforward as well;
3) The OPERATOR keyword instead of PROCEDURE/FUNCTION for methods Invoke -- should we choose to require it -- would be somewhat more complicated.
--
βþ
-------------- next part --------------
# HG changeset patch
# User Blaise.ru
# Date 1640402948 -10800
# Sat Dec 25 06:29:08 2021 +0300
+ Functors: applying round brackets to instances calls their method Invoke
diff -r 3ecaef5e9a49 -r 0ac7231ddc94 pexpr.pas
--- a/pexpr.pas Sat Dec 25 21:36:11 2021 +0300
+++ b/pexpr.pas Sat Dec 25 06:29:08 2021 +0300
@@ -2762,45 +2762,73 @@
else
begin
- { is this a procedure variable ? }
- if assigned(p1.resultdef) and
- (p1.resultdef.typ=procvardef) then
- begin
- { Typenode for typecasting or expecting a procvar }
- if (p1.nodetype=typen) or
- (
- assigned(getprocvardef) and
- equal_defs(p1.resultdef,getprocvardef)
- ) then
+ if assigned(p1.resultdef) then
+ case p1.resultdef.typ of
+ { a procedural variable }
+ procvardef:
begin
- if try_to_consume(_LKLAMMER) then
+ { Typenode for typecasting or expecting a procvar }
+ if (p1.nodetype=typen) or
+ (
+ assigned(getprocvardef) and
+ equal_defs(p1.resultdef,getprocvardef)
+ ) then
begin
- p1:=comp_expr([ef_accept_equal]);
- consume(_RKLAMMER);
- p1:=ctypeconvnode.create_explicit(p1,p1.resultdef);
+ if try_to_consume(_LKLAMMER) then
+ begin
+ p1:=comp_expr([ef_accept_equal]);
+ consume(_RKLAMMER);
+ p1:=ctypeconvnode.create_explicit(p1,p1.resultdef);
+ end
+ else
+ again:=false
end
else
- again:=false
- end
+ begin
+ if try_to_consume(_LKLAMMER) then
+ begin
+ p2:=parse_paras(false,false,_RKLAMMER);
+ consume(_RKLAMMER);
+ p1:=ccallnode.create_procvar(p2,p1);
+ { proc():= is never possible }
+ if token=_ASSIGNMENT then
+ begin
+ Message(parser_e_illegal_expression);
+ p1.free;
+ p1:=cerrornode.create;
+ again:=false;
+ end;
+ end
+ else
+ again:=false;
+ end;
+ end;
+ { a functor }
+ recorddef, objectdef:
+ if (token=_LKLAMMER)
+ and (m_closures in current_settings.modeswitches)
+ then
+ begin
+ structh:=tabstractrecorddef(p1.resultdef);
+ if structh.typ=objectdef then
+ searchsym_in_class(tobjectdef(structh),tobjectdef(structh),'INVOKE',srsym,srsymtable,[ssf_search_helper])
+ else
+ searchsym_in_record(trecorddef(structh),'INVOKE',srsym,srsymtable);
+ if assigned(srsym) and (srsym.typ=procsym) then
+ do_proc_call(srsym,srsymtable,structh,false,again,p1,[],nil)
+ else
+ begin
+ // TODO: BETTER ERROR
+ Message1(sym_e_id_no_member,'method Invoke');
+ p1.free;
+ p1:=cerrornode.create;
+ again:=false;
+ end;
+ end
+ else
+ again:=false;
else
- begin
- if try_to_consume(_LKLAMMER) then
- begin
- p2:=parse_paras(false,false,_RKLAMMER);
- consume(_RKLAMMER);
- p1:=ccallnode.create_procvar(p2,p1);
- { proc():= is never possible }
- if token=_ASSIGNMENT then
- begin
- Message(parser_e_illegal_expression);
- p1.free;
- p1:=cerrornode.create;
- again:=false;
- end;
- end
- else
- again:=false;
- end;
+ again:=false;
end
else
again:=false;
More information about the fpc-devel
mailing list