Skip to content

Commit 3b5d04b

Browse files
authored
PPC 0022 Metaprogramming API (#25)
1 parent 59ef6e5 commit 3b5d04b

File tree

1 file changed

+367
-0
lines changed

1 file changed

+367
-0
lines changed

ppcs/ppc0022-metaprogramming.md

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
# Metaprogramming API
2+
3+
## Preamble
4+
5+
Author: Paul Evans <PEVANS>
6+
Sponsor:
7+
ID: 0022
8+
Status: Exploratory
9+
10+
## Abstract
11+
12+
A coherent API for metaprogramming tasks; that is, code which is aware of its own code structure and can inspect or manipulate it.
13+
14+
## Motivation
15+
16+
As a dynamic language, programs written in Perl have the ability to be aware of the structure of their own code. Functions, packages, variables, and other constructions can be obtained or manipulated by name. While the techniques can certainly be over-used, it is a useful and necessary ability of what it means to be a "dynamic language", and where appropriate these kinds of techniques should be supported and encouraged.
17+
18+
Originally, Perl provided some abilities in the form of operations on glob references. Gradually over time, authors have required more abilities than this and increasingly have used a variety of CPAN modules to extend these abilities. As many of the operations provided ought to be considered fundamental language abilities for a dynamic language, it would be better if these were provided by core Perl itself.
19+
20+
This PPC aims to provide a set of API functions to cover many of these abilities. Similar to the case of PPC 0009 having added a namespace for general "builtin" functions, it is not intended that this PPC covers a total and complete list; but instead sets out an initial set of functions. It is expected that more would be added over time on an individual basis, as the need arises. In particular, this PPC does not address any additional API that may be needed by the new object system currently being developed under the `use feature 'class'` flag.
21+
22+
## Rationale
23+
24+
(explain why the (following) proposed solution will solve it)
25+
26+
## Specification
27+
28+
As this PPC adds a number of functions all on an overall-similar theme, they are all collected into a new toplevel namespace. For now this namespace is called `meta::`; though as per the rest of the details in this PPC that is currently very much up for debate.
29+
30+
Objects that are returned by these functions will be instances of some named package within the `meta::` namespace, though we do not yet specify exactly what they will be. Furthermore we make no statement about what actual container type of objects those actually are. Users should not attempt to interact with these objects except via the API specified here.
31+
32+
The get-like functions come in pairs, consisting of a function named `get_...` and one called `can_...`. If the target item exists, then in either case it is returned. The behaviour differs when the item does not exist. The `get` variant will throw an exception, whereas the `can` variant will return `undef`. This allows the caller to decide which behaviour is best for the use-case.
33+
34+
It is currently still an undecided question on what ought to be the correct behaviour for `add`-like functions when asked to create something that already exists, or the `remove`-like functions when asked to remove something that doesn't exist. It may be solved by something similar to above, where there are two differently-named functions that differ in that case. As yet there aren't any particularly compelling solutions.
35+
36+
### Functions to operate on Packages
37+
38+
These are functions to create, manipulate, and inspect packages themselves.
39+
40+
#### `get_package`
41+
42+
```perl
43+
$metapackage = meta::get_package($name);
44+
```
45+
46+
Returns a meta-package object instance to represent the named package, if such a package exists. If not an exception is thrown.
47+
48+
[[TODO: Decide whether "all packages exist", and thus this can never fail]]
49+
50+
#### `add_package`
51+
52+
```perl
53+
$metapackage = meta::add_package($name);
54+
```
55+
56+
Creates a new package of the given name, and returns a meta-package object instance to represent it.
57+
58+
#### `list_packages`
59+
60+
```perl
61+
@metapackages = meta::list_packages($basename);
62+
```
63+
64+
Returns a list of sub-packages within the given base package name, each as a meta-package object instance.
65+
66+
### Functions to operate on References
67+
68+
Some less common functions that may still be useful in some situations. These functions can be used to obtain meta-programming object instances in ways other than walking the symbol table for given names.
69+
70+
#### `for_reference`
71+
72+
```perl
73+
$metavar = meta::for_reference($ref);
74+
```
75+
76+
Given a reference to a package variable or subroutine, returns a meta-object instance to represent it. If `$varref` is not a GLOB, SCALAR, ARRAY, HASH or CODE reference, an exception is thrown.
77+
78+
Anonymous subroutines should be supported; at least in order to obtain a meta-object to represent them so that methods like `set_subname` can be called on them.
79+
80+
*Note:* It is currently unspecified what happens if the reference refers to a lexical or anonymous variable, rather than a package one. This PPC does not specify any useful behaviour for such instances, but it's possible that future abilities may be added that become useful on lexical variables or anonymous containers.
81+
82+
### Methods on Meta-Package Objects
83+
84+
These methods all operate on a meta-package object obtained by one of the above functions.
85+
86+
#### `name`
87+
88+
```perl
89+
$name = $metapackage->name;
90+
```
91+
92+
Returns the fully-qualified name of the package as a plain string. This will match the string passed to `meta::get_package` used to obtain it, for example.
93+
94+
#### `can_symbol`, `get_symbol`
95+
96+
```perl
97+
$metasymbol = $metapackage->can_symbol($name);
98+
99+
$metasymbol = $metapackage->get_symbol($name);
100+
```
101+
102+
Returns a meta-symbol object instance to represent the symbol within the package, if such a symbol exists. If not either `undef` is returned or an exception is thrown.
103+
104+
The exact type of symbol that is returned depends on the first character of the `$name` parameter - i.e. the sigil. Sigils of `*`, `$`, `@`, `%` and `&` respectively will return a meta-symbol representing a glob itself, or the scalar, array, hash or code slots from it.
105+
106+
(Direct access to the scalar, array, hash or code slots of the glob is provided as a shortcut convenience for the common cases. Access to other more exotic slots of the glob such as filehandles or formats will have to be performed via the metaglob object itself).
107+
108+
#### `add_symbol`
109+
110+
```perl
111+
$metasymbol = $metapackage->add_symbol($name, $value);
112+
```
113+
114+
Creates a new named symbol. If a value is passed it must be a reference to an item of the compatible type. If no initialisation is provided for variables, an empty variable is created. If no initialisation is provided for a subroutine then a forward declaration is created which has no body yet.
115+
116+
#### `remove_symbol`
117+
118+
```perl
119+
$metapackage->remove_symbol($name);
120+
```
121+
122+
Removes a symbol. Nothing is returned.
123+
124+
#### `list_symbols`
125+
126+
```perl
127+
@metasymbols = $metapackage->list_symbols($filter)
128+
```
129+
130+
Returns a list of symbols in the given package, in no particular order. (And in particular, we make no guarantees about the order of the results as compared to the order of the source code which declared it).
131+
132+
TODO: Define what the filters look like. Need to be able to select scalar/array/hash/code as well as sub-stashes. Actually, do we need sub-stashes if we have `list_packages`?
133+
134+
### Methods on Meta-Symbol Objects
135+
136+
Several of the above methods return instances of a meta-symbol object. In practice, any meta-symbol object will represent either a glob (when using the "\*" sigil), variable (when using the "$@%" sigils), or a subroutine (when using the "&"). Each of these cases is described further below. The following methods are available on all kinds.
137+
138+
#### `basename`
139+
140+
```perl
141+
$name = $metasymbol->basename;
142+
```
143+
144+
Returns the final part of the symbol's fully qualified name; the name within the package after the final `::` but **excluding** the leading sigil.
145+
146+
#### `sigil`
147+
148+
```perl
149+
$sigil = $metasymbol->sigil;
150+
```
151+
152+
Returns the single-character sigil that leads the symbol's name. This will be one of "\*", "$", "@", "%" for variables or "&" for subroutines.
153+
154+
#### `package`
155+
156+
```perl
157+
$metapackage = $metasymbol->package;
158+
```
159+
160+
Returns the meta-package object instance representing the package to which this symbol belongs.
161+
162+
#### `name`
163+
164+
```perl
165+
$name = $metasymbol->name;
166+
```
167+
168+
Returns the fully-qualified name for this symbol, including its sigil and package name. This would be equivalent to
169+
170+
```perl
171+
$name = $metasymbol->sigil . join "::", $metasymbol->package->name, $metasymbol->basename;
172+
```
173+
174+
#### `is_`*\**
175+
176+
```perl
177+
$bool = $metasymbol->is_glob;
178+
$bool = $metasymbol->is_scalar;
179+
$bool = $metasymbol->is_array;
180+
$bool = $metasymbol->is_hash;
181+
$bool = $metasymbol->is_subroutine;
182+
```
183+
184+
Returns true in each of the four cases where the object represents the given type of symbol, or false in the other three cases.
185+
186+
These methods should not be considered an exhaustive selection. There may be object types to represent other glob slots such as filehandles, formats, and so on. As more types are defined, more methods would be added to distinguish them.
187+
188+
### Methods on Meta-Glob Objects
189+
190+
These methods are available on any meta-symbol object that represents a glob - i.e. one whose sigil is "\*".
191+
192+
#### `can_scalar`, `get_scalar`
193+
194+
```perl
195+
$metasymbol = $metaglob->can_scalar;
196+
$metasymbol = $metaglob->get_scalar;
197+
```
198+
199+
Returns the meta-symbol representing the scalar slot of the glob. If the slot has not been allocated, returns `undef` or throws an exception.
200+
201+
Similar methods would exist for the other slot types; `array`, `hash`, `code` at least.
202+
203+
The various methods on the package *could* be implemented indirectly via these objects. For example something such as the following:
204+
205+
```perl
206+
method can_symbol($name)
207+
{
208+
my ($sigil, $basename) = m/^([\*\$\@\%\&])(.*)$/;
209+
my $glob = $self->can_glob($basename) or return undef;
210+
$sigil eq "\*" and return $glob;
211+
$sigil eq "\$" and return $glob->can_scalar;
212+
$sigil eq "\@" and return $glob->can_array;
213+
...
214+
}
215+
```
216+
217+
#### `add_scalar`
218+
219+
```perl
220+
$metasymbol = $metaglob->add_scalar($ref);
221+
```
222+
223+
Creates a scalar variable and adds it to the glob, returning a meta-symbol object to represent it. Optionally a reference to an existing item of the compatible type can be provided.
224+
225+
Likewise, similar would exist for the other slot types.
226+
227+
#### `remove_scalar`
228+
229+
Removes the scalar slot from the glob.
230+
231+
Likewise, similar would exist for the other slot types.
232+
233+
### Methods on Meta-Variable Objects
234+
235+
These methods are available on any meta-symbol object that represents a variable - i.e. one whose sigil is "$", "@" or "%".
236+
237+
#### `value`
238+
239+
```perl
240+
$scalar = $metavar->value;
241+
@array = $metavar->value;
242+
%hash = $metavar->value;
243+
244+
$count = scalar $metavar->value;
245+
```
246+
247+
Returns the current value of the variable. Behaves as the variable itself would in either scalar or list context. Scalars variables yield their value in both scalar and list context. Arrays and hashes yield the count of items contained within when in scalar context, or a list of their (keys and) values in list context.
248+
249+
#### `reference`
250+
251+
```perl
252+
$ref = $metavar->reference;
253+
```
254+
255+
Returns a SCALAR, ARRAY or HASH reference to the variable itself. This allows the caller to potentially modify the variable, or perform operations on it other than simply listing all its values with `value`.
256+
257+
```perl
258+
foreach my $k ( keys $metavar->reference->%* ) { ... }
259+
260+
push $metavar->reference->@*, @more_items;
261+
```
262+
263+
### Methods on Meta-Subroutine Objects
264+
265+
These methods are available on any meta-symbol object that represents a subroutine - i.e. one whose sigil is "&".
266+
267+
#### `subname`
268+
269+
```perl
270+
$name = $metasub->subname;
271+
```
272+
273+
For regular subroutines obtained from the symbol table, returns the fully-qualified name of the function including its package name but *excluding* its leading "&" sigil.
274+
275+
For previously-anonymous subroutines that have since been given a name by `set_subname`, returns that name.
276+
277+
(Inspired by `Sub::Util::subname` and to some extent `Sub::Identity`)
278+
279+
#### `set_subname`
280+
281+
```perl
282+
$metasub->set_subname($name);
283+
# returns $metasub
284+
```
285+
286+
Sets the subname for a previously-anonymous subroutine, such as one generated by `sub { ... }` syntax.
287+
288+
TODO: Specify what happens if you try to do this on a regular symbol table subroutine. We probably shouldn't allow it.
289+
290+
(Inspired by `Sub::Util::set_subname`)
291+
292+
#### `prototype`
293+
294+
```perl
295+
$prototype = $metasub->prototype;
296+
```
297+
298+
Returns the function prototype string, or `undef` if there is none set.
299+
300+
#### `set_prototype`
301+
302+
```perl
303+
$metasub->set_prototype($prototype)
304+
# returns $metasub
305+
```
306+
307+
Sets a new value for the prototype string of the function, or removes it if the given value is `undef`.
308+
309+
Returns the meta-subroutine object instance itself so as to be useful for chaining.
310+
311+
(Inspired by `Sub::Util::set_prototype`)
312+
313+
## Backwards Compatibility
314+
315+
As this PPC only adds new functions in a new namespace there are not expected to be any large concerns about backwards compatibility. The individual abilities added are all things that perl already has the ability to do - either natively, or with the help of existing core or CPAN modules - so these functions are not able to put the interpreter into any new states that could not already be encountered.
316+
317+
Additionally, the entire API specified by this PPC should be possible to implement on top of existing perl core using only an additional XS module. As such it would be easily possible to provide it as a dual-life XS module on CPAN for the benefit of at least a few previous versions of perl.
318+
319+
## Security Implications
320+
321+
The point of all these functions is to provide an ability for a program to operate on its own internals. With that ability comes the inherent danger of operating on "the wrong bits"; parts that were not intended. This becomes especially important to consider when user-supplied values are being used as parts of the arguments to operate on. Care should be taken to ensure that arguments cannot be constructed so as to escape the intended areas that the program wishes to expose for manipulation. Some comparison with the dot-in-@INC issue may be relevant.
322+
323+
Some of these issues can be mitigated by careful use of the metapackage objects. For instance, a program wishing to expose symbols to user code only within a certain package can obtain the meta-package object for that package using a fixed static string, and then know that any symbol-access methods invoked on that object can only operate on symbols within that one package.
324+
325+
## Examples
326+
327+
(Should find some bigger use-cases and write them here.)
328+
329+
## Prototype Implementation
330+
331+
Several CPAN modules provide inspiration for abilities and function names:
332+
333+
* [`Package::Stash`](https://metacpan.org/pod/Package::Stash)
334+
335+
* [`Object::Pad::MOP::Class`](https://metacpan.org/pod/Object::Pad::MOP::Class) and related.
336+
337+
## Future Scope
338+
339+
* Currently this PPC makes no consideration for whatever extra abilities may be needed for `feature 'class'`. It is expected that additional abilities will be wanted that can operate on classes and members of them (fields, methods, etc...). An insipration can be taken from `Object::Pad::MOP::Class` and related. The extensible design of the meta-objects provided here should make those additions easy by simply adding more methods and classes to represent them.
340+
341+
* A tangentially-related topic is that of extended API/semantics for declarative attributes. The existing Perl attributes on variables and subroutines are nowhere near as powerful as the ones that will eventually be required by `feature 'class'` and are already implemented by `Object::Pad`. Whatever extensions are added to core perl to eventually support this will likely have meta-programming accessor methods associated with these object instances too.
342+
343+
* The set of functions provided here is fairly minimal, and no convenient shortcuts are currently considered. As use-cases arise, it may turn out that common patterns, such as reading the value of a given variable in a given package, turn out to be useful. Wrapper functions around these common behaviours could be added on a case-by-case basis.
344+
345+
## Rejected Ideas
346+
347+
* PadWalker's entire API of `peek_my`, `peek_our` and `peek_sub`, `closed_over`, `set_closed_over`.
348+
349+
These do not seem useful at the current time.
350+
351+
* B::Hooks::EndOfScope
352+
353+
* B::CompilerPhase::Hook
354+
355+
It may be useful to add support for these kinds of abilities somewhere in core perl, but this metaprogramming PPC does not seem to be the place for them.
356+
357+
## Open Issues
358+
359+
* Currently it is unspecified what the "add"-type functions will do if an existing item is already found under the proposed new name. Do they warn before replacing? Is it an error and the user must delete the old one first? Should we provide a "replace"-type function, for which it is an error for the original *not* to exist? Or maybe the semantic should be "add-or-replace" silently.
360+
361+
* Relatedly, it is unspecified what the "remove"-type functions will do if asked to remove an item that does not even exist. Should it fail, or should it just silently accept that it doesn't have to do anything?
362+
363+
## Copyright
364+
365+
Copyright (C) 2022-2023, Paul Evans.
366+
367+
This document and code and documentation within it may be used, redistributed and/or modified under the same terms as Perl itself.

0 commit comments

Comments
 (0)