Open rwaldron opened 6 years ago
I was originally thinking that the caller would set this
and pass the right methods that are supported. For example:
function a(block) {
block.call({
b: function(block) {
// ...
}
});
}
Enabling
a {
b {
}
}
To be equivalent to:
a(function() {
this.b(function() {
})
})
However, in this discussion I think I'm going back to my original formulation, which was to desugar things as:
a {
b {
}
}
// equivalent to
a(function() {
b.call(this, function() {
})
})
In this formulation, a
would still be possible to pass a reference to b
such that a use case like the following to work:
select (foo) {
case (bar) { ... }
}
WDYT?
Just thought I'd drop in and note that there is also some relevant discussion in #13, primarily starting here.
a { b { } } // equivalent to a(function() { b.call(this, function() { }) })
I addressed this in 1 and 2 of my first comment: this
, as in b.call(this, function() {
, is undefined
in strict mode code and global
in non-strict mode code.
@rwaldron Not if a
calls its callback like func.call(inst, ...)
. a
sets this
, and it's only undefined
or global
if a
calls it like func()
. this
isn't bound, even though it looks like it should be.
It still falls into the trap of making nested DSL calls ambiguous.
Not if a calls its callback like func.call(inst, ...). a sets this
I explicitly addressed this in number 3 of my first comment.
I addressed this in 1 and 2 of my first comment: this, as in b.call(this, function() {, is undefined in strict mode code and global in non-strict mode code.
Can you help me understand what you mean here? Specifically:
b.call(this, function() {, is undefined in strict mode
Can you help me understand what is undefined
? For example:
(function() { "use strict"; function b() { console.log(this) } b.call({c: 1}) })()
// > {c: 1}
Allows b.call({c:1})
to pass a this
reference in strict mode
.
WRT
I explicitly addressed this in number 3 of my first comment.
and
(2) falls down when the user defined functions have an explicit this object set:
Yes, you are correct that if a user-defined function was bound per var b = unbounda.bind(thisObject);
the this
reference would be changed as a
would call b.call(notherthis)
.
I think that's working as intended, in that's part of the contract for the functions that take block params in that they cannot assume that the bindings would be kept (and would rather point to the parent).
Can you help me understand what is undefined? For example:
(function() { "use strict"; function b() { console.log(this) } b.call({c: 1}) })() // > {c: 1}
Allows b.call({c:1}) to pass a this reference in strict mode.
Of course it does, because you used call({c:1})
, but this feature cannot assume that such a thing will always work: if b
is an arrow function or the result of fn.bind(...)
, then b.call({...})
will have no effect:
(function() { "use strict"; let b = () => { console.log(this) }; b.call({c: 1}) })()
// undefined
(function() { "use strict"; function f() { console.log(this) }; let b = f.bind({ d: 1}); b.call({c: 1}) })()
// { d: 1 }
t if a user-defined function was bound per
var b = unbounda.bind(thisObject);
thethis
reference would be changed asa
would callb.call(notherthis)
.
I don't understand what you're saying here. Once var b = unbounda.bind(thisObject);
occurs, the bound this
of b
can never be changed again, it cannot be overridden by a b.call(...)
in that's part of the contract for the functions that take block params in that they cannot assume that the bindings would be kept
If you're telling me that block params can change the bound this
of a function object, then I believe there is an object-capability security violation. @erights can you check my assessment?
If you're telling me that block params can change the bound this of a function object, then I believe there is an object-capability security violation. @erights can you check my assessment?
Yes. While I am supportive of the overall direction, @samuelgoto knows that I am against the specific this
binding semantics he's proposing. Once that's fixed, the blocks should expand to arrow functions rather than function
functions, as arrow functions are already much closer to TCP. (The remaining TCP violations still must be statically prohibited or fixed, but that's another matter.)
Of course it does, because you used call({c:1}), but this feature cannot assume that such a thing
will always work: if b is an arrow function or the result of fn.bind(...), then b.call({...}) will have no effect:
I understand that if b is an arrow function or the result of fn.bind, then b.call() will have no effect. I think that, perhaps, what I am genuinely confused about, is that the feature is meant to be used primarily from the newly introduced syntax:
a() { // <- this is a block param. neither an arrow function or a previously bound function
//
}
Is your point that the function a
here cannot assume / observe that / whether the parameter that was passed was passed via the newly introduced syntax? Example:
a(() => { "hello" });
function a(block) {
// I cannot assume that block.call() will have any effect because block may
// have been passed as an arrow function or as a previously bound function.
}
Did I understand that correctly? Is that the point that you are trying to make?
(oops, sorry for closing/reopening, pressed the wrong button)
Just reporting back on this thread here with what I think was forward progress made in this thread.
I'm generally in agreement with the desire to move away from the this
nesting mechanism as well as the with
-like scoping mechanism to find the variable names.
Just to give context, the use case that I think represents why we need a nesting mechanism is select
and when
: they are attached to one another in some way and need to pass information back and fourth. Here is an example:
select (expr) {
when (cond) {
// execute this block if cond == expr.
}
}
This was initially proposed as a series of nested function() {}
s and passing this
around, which, like it was pointed out earlier, creates all sorts of challenges.
Looking a bit into what this could look like with arrow functions, here is what we explored in the other thread:
this
::
from the bind operator) to connect with the "special" argumentFor example, this is what you write instead:
select (expr) {
::when(cond) {
// ... this gets executed if expr == cond ...
}
}
Which gets transpiled as:
select (expr, (__parent__) => {
__parent__.when(cond, (__parent__) => {
// .. gets executed if expr == cond ...
});
})
This gives select
the ability to choose which implementation of when
to be used, because ::
looks at methods in an object that gets passed to the block param from select
. For example:
function select(expr, block) {
block({
// this is the "when" implementation that gets accessed when called like ::when() {}
when(cond, inner) {
if (expr == cond) {
inner();
}
}
});
}
Does that address some of the concerns raised here with regards to scoping and this
?
Similar discussion here too:
https://github.com/samuelgoto/proposal-block-params/issues/21#issuecomment-347964929
Related to #24
EDIT:
The readme has been updated since this was first posted, however the changes made do not sufficiently address all of the scoping problems. Ref: https://github.com/samuelgoto/proposal-block-params/commit/3280e50fb3a18cfa29f6755302ca568434a49b8e
Original follows the break
Re: the example from the readme:
What is
this
inside the callback function? In strict mode code, that desugaring has nothis
unless explicitly bound:The example does work with non-strict mode code, but also assumes that
b
was created via VariableStatement or FunctionDeclaration in the top level scope:If
b
was created as a LexicalDeclaration, it won't have a binding on globalthis
object:(2) falls down when the user defined functions have an explicit
this
object set: