-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
with
-like performance concerns
#13
Comments
In the current formulation, every nested block gets called with "this". It is not a with like resolution: lexically, it tells where to look at things. For example:
gets desugared to: server (app, function() {
// "this" here gets introduced because it is a block-param call AND it is nested lexically
this.get("/route", function() {
});
}) So, no, I don't expect this to have "with"-like resolution semantics (and performance implications and design considersations that go along with "with"-like semantics). Does that make sense? |
What is |
App is the first argument to the call to server. It is defined earlier. |
I'm referring to this hypothetical scenario: const app = express()
server(app) {
get("/route") {
app // Is this the Express app or `this.app`?
}
} |
In the current formulation, const app = express()
server(app) {
get("/route") {
// This is the express app NOT this.app. Normal identifier
// resolution/scoping applies.
app
// The only change in the current formulation is to pre-pend "this"
// to inner block params.
// e.g.
// app { ... }
// Would be equivalent to
// this.app(function() { ... })
// so, unless it is structured as a block param call, it is evaluated
// with the current semantics.
}
} |
Oh okay. What if Also, this precludes potential use cases with nested DSLs, where you could hypothetically have in the browser a routing library, then a |
If this.app is not defined, it is equivalent to let a = undefined; a();.
That is, it throws.
Ah, good point about nesting DSLs. A couple of options:
1) The nested DSL uses the binding operator: e.g. ::render(elem) {}
2) The nested DSL somehow registers with the outer DSL. e.g. @component
class MyElement extends React.Component { ... } with annotations.
Do you think that would be sufficient?
…On Tue, Oct 31, 2017 at 2:42 AM, Isiah Meadows ***@***.***> wrote:
Oh okay. What if this.app is not defined? What's the fallback?
Also, this precludes potential use cases with nested DSLs, where you could
hypothetically have in the browser a routing library, then a render(elem)
{ ... } call inside to render your view for that particular route. You
could solve this by making the top-level DSL invocation explicit and
differentiable from a normal call.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#13 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAqV6vyMo6i30jmVy-O0XU3nYlhWkuT9ks5sxut4gaJpZM4QKNBf>
.
--
f u cn rd ths u cn b a gd prgmr !
|
Okay, that works as expected.
I'd strongly prefer the first option (independent of syntax) with nesting DSLs, since it avoids making the outer DSL's responsibility to make sure it gets invoked correctly. Also, for consistency and reduced confusion, we should require that top-level DSLs also use the same operator nested DSLs use. That way, it's 1. clear you're using a DSL and not just plain JS, and 2. there's only one syntax. |
This thread just made me realize that my current formulation registering DSLs isn't going to work very well. Specifically, some use cases are not DSLs at all, but are control structures. For example: foreach ({item, index} in array) {
unless (index % 2 == 0) {
item.odd = true;
}
} Here, My previous formulation, was something along the lines of: a {
b {
}
} Would translate to: a(function() {
b.call(this, function() {
...
});
}) Which would enable this case to work as: foreach (array, function({item, index}) {
unless.call(this, index % 2 == 0, function() {
item.odd = true
})
}) As opposed to: foreach (array, function({item, index}) {
// this.unless is defined in the call from foreach, which implies
// it needs to be aware of unless's implementation which
// doesn't sound desirable.
this.unless(index % 2 == 0, function() {
item.odd = true
})
}) This would enable |
And this is why on my first read, I had `with`-related concerns. It seemed
to imply dynamic DSLs (`this.foo(...)`) while it stated static resolution
(`foo.call(this, ...)`), seemingly confusing the two.
…On Wed, Nov 1, 2017, 16:10 sam goto ***@***.***> wrote:
This thread just made me realize that my current formulation: registering
DSLs isn't going to work very well. Specifically, some use cases are not
DSLs at all, but are control structures. For example:
foreach ({item, index} in array) {
unless (index % 2 == 0) {
item.odd = true;
}
}
Here, foreach and unless are independently provided functions that take
block lambdas, so, they are unaware of themselves.
My previous formulation, was something along the lines of:
a {
b {
}
}
Would translate to:
a(function() {
b.call(this, function() {
...
});
})
Which would enable this case to work as:
foreach (array, function({item, index}) {
unless.call(this, index % 2 == 0, function() {
item.odd = true
})
})
As opposed to:
foreach (array, function({item, index}) {
// this.unless is defined in the call from foreach, which implies
// it needs to be aware of unless's implementation which
// doesn't sound desirable.
this.unless(index % 2 == 0, function() {
item.odd = true
})
})
This would enable foreach and unless to be provided independently while
still having access to this.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#13 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AERrBK_z74M0x5LbEIH6QF_d9bd6BZI8ks5syNAfgaJpZM4QKNBf>
.
|
Ah, I see now what you mean by This formulation could deal with both cases, but would introduce complexity: a {
// ...
b {
// ...
}
// ...
} desugaring to: a(function() {
// ...
(b in this ? this.b : b)(function() {
// ...
})
// ...
}) Method resolution takes precedence over global/function resolution. Note that, unlike
How does this sound? |
FWIW, this approach is consistent with Kotlin: it resolves function identifiers looking at "this" first before looking at the global scope. Here is an example: // This does not get called, unless we remove the "case" method
// in Select.
fun case(condition: Boolean, block: () -> Unit) {
println("This does not called!")
block()
}
fun select(condition: Boolean, block: Select.() -> Unit) {
var sel = Select();
sel.block()
}
class Select {
// If this method wasn't here, it would've used
// the global function case.
fun case(condition: Boolean, block: () -> Unit) {
println("This gets called!")
block()
}
}
fun main(args: Array<String>) {
var expr: Boolean = true;
select (expr) {
case (true) {
println("Totally true")
}
case (true) {
println("Totally false")
}
}
} |
I'm still not a fan, because even though you've restricted it to only the calls, it's still sitting in a Kotlin has static types and method names are always resolved statically, so it can compile everything down to optimized static calls (if they're not IMHO, an explicit "this starts a DSL" with nested variants being implicit would be best. |
And in response to a few another parts:
Both. With Kotlin, you can always verify which type a method comes from, but with JS, it might not be so clear (especially if
That's probably better written as this, using // This does not get called, unless we remove the "case" method
// in Select.
inline fun case(condition: Boolean, block: () -> Unit) {
println("This does not called!")
block()
}
class Select {
// If this method wasn't here, it would've used
// the global function case.
inline fun case(condition: Boolean, block: () -> Unit) {
println("This gets called!")
block()
}
}
inline fun select(condition: Boolean, block: Select.() -> Unit) {
block(Select())
}
fun main(args: Array<String>) {
val expr = true
select (expr) {
case (true) {
println("Totally true")
}
case (false) {
println("Totally false")
}
}
}
// Normally, you'd use `when` instead:
fun main(args: Array<String>) {
val expr = true
when (expr) {
true => println("Totally true"),
false => println("Totally false"),
}
} |
Originally, I had the following desugaring: a {
b {
}
} Would be rewritten as a(function() {
b.call(this, function() {
})
}) Which would avoid the The drawback, is that it requires you to |
The implicit
this
binding spurs somewith
-related performance concerns. The callback body could, in sloppy mode, be desugared almost precisely to this:For well-known reasons at this point, engines have severely struggled to optimize
with
, and I suspect they'll have similar objections to this.Here's a few potential solutions:
If a local binding does not exist, then fall back to the
with
-like behavior. Engines can continue to optimize existing variable accesses, deferring the scope check to globals vsthis
(which already requires an unavoidable runtime check)Require some sort of prefix/sigil like either one of these (or something completely different altogether):
(Note: the second does not conflict with decorators, since you can't "decorate" blocks.)
Drop the implicit
this
binding in nested blocks altogether and just require users to usethis.foo
.The text was updated successfully, but these errors were encountered: