|
| 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