Re: Initializing constants once, with code?
| From: | Larry Garfield | Date: | Mon, 06 Jan 2020 18:07:32 +0000 |
| Subject: | Re: Initializing constants once, with code? | ||
| References: | 1 2 3 4 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-108008@lists.php.net to get a copy of this message | ||
Lots to reply to here, so I'm going to munge it into a single response.
Mike Schinkel said:
> Let's consider the fact that is PHP had never had the define() and instead implemented the
>
const keyword from the start, and only allowed compile time values.
> If PHP had done that, you would be arguing from a status quo bias the same thing about making that
> dynamic. And it currently is dynamic which IMO invalidates your argument that constants must be
> defined at compile time.
Do not put words in my mouth and presume to know what I would think in some hypothetical situation.
That is rude and inappropriate.
You also repeatedly are accusing Rowan and I of "status quo bias" as though it were a bad
thing. Let me be clear: I entirely agree with Zeev that in a language used by millions of people a
"status quo bias" is a *very good thing*. The potential benefit of any improvement needs
to substantially outweight any downsides, and the burden of proof is on the person/people proposing
a change. There's a long list of things I'd like to see added to the language myself, but
I'm well aware that the burden of proof is always on the proposer (in this case, you), as it
should be.
Also Mike:
>> Many people would say the kind of global state you're talking about is an
>> anti-pattern.
> Well, Larry did, which is reality is an appeal-to-authority argument and not an argument on the
> merits.
So you call something an anti-pattern, then get on my case for "appeal to authority" for
calling something an anti-pattern? Your argument style needs work, dude.
Global data is well-recognized across the industry as dangerous, and the larger the system the more
dangerous it is. It violates pure functions, and pure functions are how you get any semblance of
predictability in your code. I do not have the time or inclination to go through all the details of
that here; you can find ample resources for that yourself.
(And for the record, "appeal to authority" is only a logical fallacy if the authority
being appealed to is not a qualified authority on the topic at hand. Appealing to Tiger
Woods's authority on golf clubs is not a logical fallacy because he really would have more
knowledge and experience than most; appealing to his authority on baseball gloves would be a logical
fallacy.)
> I take it you have never used Pantheon hosting then? You create a named environment to match a
> Git branch.
Full disclosure: I work for Platform.sh, which has been doing environment-per-bit-branch longer than
Pantheon has. I'm responsible for maintaining our public utility libraries for bridging
environment variables into application configuration for environment-sensitive things like DB
credentials and route names. So I am very intimately familiar with this problem space, probably
moreso than most people. :-)
In my experience, systems that configure themselves via constants are the worst. I know some do,
but it's the most annoying option to work with. It makes any sort of dynamic override (which
you *must* do in a cloud hosting enviroment) a PITA. The best option are systems that are
configured by env vars, and have a very clearly set of documented env vars to populate. Env vars
are also globally readable but you're not tricked into thinking they're compiled out at
compilation time (as things with const are.) They're also by far the easiest for
me to work with as the person writing that glue code.
Adding language features that make it easier for people to take the approach that is in my
experience the most painful option is something I am not going to support. :-)
To the use cases, "I'm changing the API but don't want to change the API" (what
you're calling evolving here) is not an argument that's going to carry weight with me.
It's not a use case I feel is worth the confusion that comes with "Sometimes a constant is
not a constant". The links you offer offer some other potential use cases that are worth
considering, but "I made something a const when I shouldn't have" is not going to be
convincing to me.
Enums are also not constants. They're sum types. They can be implemented using a collection
of constants, but that's a mediocre implementation of them (even if a popular one).
To the list of links Mike offered, let me add Wikipedia:
https://en.wikipedia.org/wiki/Constant_(computer_programming)
So, to examine the full problem space using Wikipedia's terminology:
1) Macro constants. These are handled at compile time and do not exist at runtime. This is what
PHP's const is currently, both global and class-based.
2) Dynamically valued constants. This is what Java and C++ have, and essentially what
Javascript's const does. This supports dynamic code that runs a computation to produce a
value, once. That value's scope is based on whatever that variable's scope would be
otherwise. (Could be global, could be class static, could be local to a function.) It's
unclear when that code is actually run, and likely varies by language; also, it's unclear if
it's legal to have the computing code depend on runtime information. That may also vary by
language; I'm not sure.
PHP's declare statement is more akin to the latter, albeit hoisted to global
scope. It's not computed on-demand but whenever the declare line happens to be
reached. There is no class-level equivalent.
Unlike the other languages listed, PHP also has a syntactic split between variables and constants.
Variables have a $, constants do not. Whether that's good or bad is an academic question as
it's clearly not going to change. That's in contrast to most other languages where there
is no syntactic distinction, just the convention of capitalizing not-gonna-change things.
Dynamically valued constants are... I'll say similar to but not quite the same as an
auto-memoized function. For one there's the () vs not; for another, an auto-memoized function
may take parameters and thus memoize different return values. From one perspective one could look
at dynamically valued constants as a zero parameter self-memoizing function with some syntactic
sugar.
I can see a use for const variables, especially const class variables. The example in the Java link
Mike provided is actually something I ran into just last week and it would have been very nice to
have that ability. However, class constants in PHP right now are limited to Macro constants.
So the more general request here is for dynamically valued constants beyond the current
declare support. Which... I could potentially get behind. Self-memoizing functions is
one way they could be implemented, if you don't mind (). (I don't.) That would also have
a lot of other benefits.
The caveat in both cases is dependencies. Memoizing an impure function can lead to all sorts of
time-dependent silliness. The same concern applies to dynamically valued constants. If their
generation code is a pure function (however expressed), then cool, that's a safe and nice
feature. If it has unpredictable dependencies, though, the behavior can be equally unpredicatable.
That includes depending on $_GET or env vars. (Remember, PHP has plenty of users outside of
shred-nothing requests, and with FFI and preloading hopefully more of them wil get used more often.)
So I could get behind dynamically valued constants and/or auto-memoizing functions. That would
probably mean they still have a $ on them, which doesn't bother me. That clearly separates
them visually from macro constants, which I like.
The concern is when you use impure functions to generate them, the results are unpredicatable and,
depending on the circumstances, may be non-deterministic. The potential for hard to find bugs here
is high; although the counter point is that using a function/method to emulate them today, which you
can absolutely do, offers the exact same risk. So I am nervous here but not quite agahst, since...
"status quo bias". ;-)
There's a lot of ways that could be done syntactically. One option is Tyson's proposal,
although I would prefer to not pollute const with it and use a declare, like so:
// This line already works.
declare('API_URL', $_ENV['EXAMPLE_API_URL'] ?? 'https://api.example.com');
// The change would just be allowing the const to lazy-populate here.
class Api {
const URL = API_URL;
}
Another option is to allow declare in classes:
class Api {
declare string $URL = $_ENV['EXAMPLE_API_URL'] ?? 'https://api.example.com');
}
(Or something like that.)
The trick is when exactly these run, because they're impure functions so whether they run at
code compile time or first-access could mean a dramatic difference in their resulting value. And
that is precisely why I am very, very nervous about allowing impure functions to be cached, whatever
the syntax on top of them. (Constant-like or not.)