[1] https://www.ft.com/content/03775904-177c-11de-8c9d-0000779fd...
JS allows you to dynamically modify some of the scopes that names refer to, as well as changing the actual prototype chain itself. I'm not sure you can do such crazy things with Python classes/metaclasses.
Of course, for v8 in particular, doing any of this crazy manipulation tends to set off alarm klaxons that kick your code off every optimization path, but the language still permits it.
> the Python ecosystem fairly heavily relies on CPython extension modules, and if you wish to remain compatible with them you're constrained in some ways, especially if you care about performance of calling into/from them
And for JS, very low overhead of calling into the DOM APIs (written in C++) is a necessary feature for having competitive performance. Arguably more so than in Python, since the overhead of the FFI trampoline itself here is considered a bottleneck.
You can do some fairly disgusting things to name resolution in class bodies, but names within functions are resolved statically nowadays.
> as well as changing the actual prototype chain itself
You can change a class's MRO, if that's the closest analogue.
class Foo:
x = 'foo'
class Bar:
x = 'bar'
class Baz(Foo):
pass
print(Baz.x)
Baz.__bases__ = (Bar,)
print(Baz.x)
In Python you can also hook your own entire custom import system into importlib, or just arbitrarily change the meaning of the `import` statement by replacing builtins.__import__: >>> import builtins
>>> builtins.__import__ = lambda *a: "Too bad"
>>> import foo
>>> foo
'Too bad'
You can use sys._getframe() to look in the current call stack and poke at variables: import sys
def f():
x = 3
return g()
def g():
return sys._getframe().f_back.f_locals['x']
print(f()) # 3
You can make your own class that inherits from types.ModuleType and use it to replace an existing module's class and add interesting new behaviors to its object: # foo.py
import sys, types
class MyMod(types.ModuleType):
def __call__(self):
return "Hello!"
sys.modules['foo'].__class__ = MyMod
>>> import foo
>>> foo()
'Hello!'
You can replace sys.ps1 by an object with a __str__ implementation to make a dynamic prompt in the REPL: import datetime, sys
>>> class Prompt: __str__ = lambda self: str(datetime.datetime.now()) + ' >>>'
>>> sys.ps1 = Prompt()
2019-09-12 18:33:48.303692 >>>
Python has a lot of exposed detail.> Of course, for v8 in particular, doing any of this crazy manipulation tends to set off alarm klaxons that kick your code off every optimization path, but the language still permits it.
Most of the real badness in JS (direct eval and the with statement stand out above everything else here) can be statically detected; the fact in Python that you can fundamentally change operation of things already on the call stack through prodding at things via the `sys` module makes this an order of magnitude worse (and yes, guards and OSR in principle can be used here, but it's very easy to end up with a _lot_ of guards).
> And for JS, very low overhead of calling into the DOM APIs (written in C++) is a necessary feature for having competitive performance. Arguably more so than in Python, since the overhead of the FFI trampoline itself here is considered a bottleneck.
Oh yes, it's absolutely essential, but the definition is on a very different level: we might have an interface defined in WebIDL that must be exposed to JS in a certain way, but how that's implemented is an implementation detail (and there's nothing in the public API stopping a browser from changing how their JS VM represents strings, for example; the JS VMs themselves don't really have totally stable APIs). Whereas in Python, the C API is public and includes implementation details like refcounting, string representation, etc.
Example: https://github.com/dabeaz/python-cookbook/blob/master/src/8/...
Are you talking about JS? You definitely can:
class A {}
class B extends A {}
const b = new B();
A.prototype.test = () => 'test';
b.test();
// => 'test'I ask because I know that this is something hardware “interpreters” (CISC CPU microcode decoders) do, by detecting whether the stream of CISC opcodes in the decode pipeline entirely consist of some particular uarch, and then shunting decode to an optimized decode circuit for that uarch that doesn’t need to consider cases the uarch can’t encode. But, of course, unlike hardware, software interpreters have to try to fit in a CPU’s cache lines and stay branch-predicted, so there might not be a similar win.
(Tangent: I once considered writing a compiler that takes Ruby code, rewrites the modules using only a “strict subset” of it to another language, and then either has that language’s runtime host a Ruby interpreter for the fallback, or has the Ruby runtime call the optimized modules through its FFI. I never got far enough into this to determine the performance implications; the plan was actually to enable better concurrency by transpiling Rails web-apps into Phoenix ones, switching out the stack entirely at the framework level and keeping only the “app” code, so single-request performance wasn’t actually the top-level goal.)