Skip to content

Commit 4826fd9

Browse files
author
Ovid
committed
First draft of lexical namespace PPC
1 parent a83c0fd commit 4826fd9

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

ppcs/ppc0022-lexical-require.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# A short and accurate title
2+
3+
## Preamble
4+
5+
Author: Ovid
6+
Sponsor:
7+
ID:
8+
Status: Draft
9+
10+
## Abstract
11+
12+
When writing a module, the `use` and `require` statements make the modules
13+
available globally. This leads to strange bugs where you can write
14+
`my $object = Some::Class->new` and have it work, even if you didn't
15+
require that module. This transitive dependency is fragile and will break
16+
if the code requiring `Some::Class` decides to no longer require it.
17+
18+
This PPC proposes:
19+
20+
```perl
21+
package Foo;
22+
use feature 'lexical_require`;
23+
use Some::Module;
24+
```
25+
26+
With the above, code outside of the above scope cannot see `Some::Module` unless
27+
it explicitly requires it.
28+
29+
## Motivation
30+
31+
* Accidentally relying on transitive dependencies is fragile because, unless
32+
documented, transitive dependencies are not guaranteed to exist.
33+
* Currently, loading a module injects it into a global namespace, so it's not
34+
easy to prevent this problem.
35+
* Transitive dependencies are even more fragile is the code is conditionally
36+
required:
37+
38+
```perl
39+
if ($true) {
40+
require Some::Module;
41+
}
42+
```
43+
44+
In the above, the transitive dependency can fail if `$true` is never true.
45+
46+
The initial discussion is on [the P5P mailing
47+
list](https://www.nntp.perl.org/group/perl.perl5.porters/2023/07/msg266678.html).
48+
49+
## Rationale
50+
51+
* The new syntax ensures that the module author can `require` or `use` a module and not
52+
worry that other code will accidentally be dependent on internal implementation details.
53+
54+
## Specification
55+
56+
For the given lexical scope—block or file—`use feature 'lexical_require'` will
57+
allow code to use the required module. Code _outside_ of that scope cannot use
58+
the required module unless it explicitly uses it, or there's another
59+
transitive dependency injecting that module into the global namespace.
60+
61+
```perl
62+
package Foo {
63+
use feature 'lexical_require';
64+
use Hash::Ordered;
65+
no feature 'lexical_require';
66+
use Some::Class;
67+
...
68+
}
69+
my $object = Some::Class->new; # succeeds if `Some::Class` has a `new` method
70+
my $cache = Hash::Ordered->new; # fails
71+
```
72+
73+
## Backwards Compatibility
74+
75+
This feature should be 100% backwards compatible for new code. If retrofitted
76+
into existing code, any code relying on a transitive dependency might break
77+
and need to explicitly declare that dependency.
78+
79+
These are probably going to be runtime errors, not compile-time.
80+
81+
Other than the above caveats, I am not aware of any tooling which will be
82+
negatively impacted by this. However, I don't know the
83+
[`Devel::Cover`](https://metacpan.org/pod/Devel::Cover) internals and I
84+
suspect there might be an issue there.
85+
86+
I suspect (hope), that the following will not be impacted:
87+
88+
* [`B::Deparse`](https://metacpan.org/pod/B::Deparse)
89+
* [`Devel::NYTProf`](https://metacpan.org/pod/Devel::NYTProf)
90+
* [`PPI`](https://metacpan.org/pod/PPI) (hence [`Perl::Critic`](https://metacpan.org/pod/Perl::Critic) etc)
91+
92+
## Security Implications
93+
94+
If anything, this might improve security by not allowing code to have an
95+
accidental dependency on code it doesn't explicitly use.
96+
97+
## Examples
98+
99+
From the above:
100+
101+
```perl
102+
package Foo {
103+
use feature 'lexical_require';
104+
use Hash::Ordered;
105+
no feature 'lexical_require';
106+
use Some::Class;
107+
...
108+
}
109+
my $object = Some::Class->new; # succeeds if `Some::Class` has a `new` method
110+
my $cache = Hash::Ordered->new; # fails
111+
```
112+
113+
## Prototype Implementation
114+
115+
None.
116+
117+
## Future Scope
118+
119+
In the future, it might be nice to have `namespace` declarations.
120+
121+
```perl
122+
namespace Las::Vegas;
123+
package ::Casino; # package Las::Vegas::Casino
124+
125+
```
126+
127+
For the above, what happens in `Las::Vegas` stays in `Las::Vegas`.
128+
129+
The above allows you to declare a new namespace and everything within that
130+
namespace is private to it. Only code that is officially exposed can be used.
131+
Companies can have teams using separate namespaces and only official APIs can
132+
be accessed. You can't "reach in" and override subroutines. This would require
133+
developers to plan their designs more carefully, including a better
134+
understanding of dependency injection and building flexible interfaces.
135+
136+
## Rejected Ideas
137+
138+
There really hasn't been any previous solutions on the CPAN that I've seen.
139+
I've seen closure-based solutions using lexical subs, but they're harder to
140+
write.
141+
142+
## Open Issues
143+
144+
We may have issues with test suites. They often take advantage of locally
145+
overriding/replacing a subroutine and if that's declared in a transitive
146+
dependency, it might fail.
147+
148+
## Copyright
149+
150+
Copyright (C) 2023, Curtis "Ovid" Poe
151+
152+
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)