Comparisons With JavaScript "this" and bind()

When you call a function, JavaScript sets this according to the call site:

Call style Example Value of this
Method call obj.method() this is obj
Simple call func() In non-strict mode: this is the global object (window in browsers, global in Node). In strict mode: undefined.
Constructor call new Func() this is a new object created and linked to Func.prototype
Bound call func.call(obj, ...) or func.apply(obj, ...) this is explicitly set to obj
Arrow function () => { ... } Arrow functions don’t have their own this—they close over the this from the surrounding scope.

Notes

The this value is not permanently stored in a function in JavaScript. It’s completely ephemeral, decided fresh on each call based on how the function is invoked.

The only case where a function “remembers” its this is if you used bind() or an arrow function. (bind() can set this to absolutely any value—it doesn’t have to be an object.)

If you write let f = obj.method and then call f() it has no memory of the object. Tying the this onto the function only happens in methodized calls like obj.m()

If you wanted f to remember obj as this, you’d have to explicitly bind it, e.g. let f_bound = obj.m.bind(obj);

This idea that "every function can be bound to an object, and some operations tweak it" is quite similar to what Ren-C calls a "coupling". (Couplings can be any context, e.g. OBJECT! or MODULE! or FRAME!, but not any arbitrary value like an INTEGER! or WORD!.)

Couplings are used by METHODs to connect their invocations with OBJECT!s--as one might expect.

But they can be used other ways. For instance: all RETURNs are instances of the same native function, but with different values of the coupling in their Cells. The coupling is the FRAME! which you'd want to return to, which the native can consult when it runs...because function dispatchers receive the coupling.

Ren-C slices this problem differently.

A function's dispatcher's job is to decide what (if anything) to do with a coupling in its Cell. If it's the product of a function generator like METHOD (or a specially tweaked FUNC or LAMBDA), one of the things that it might choose to do is make that coupling visible via a name in its body. If that's what it elects to do, by convention the name used is .

If there's no coupling to expose, then the closure of visibility receiving . is just the typical closure over definitions in the environment a function would expect.

obj: make object! [
    inner: method [] [
        let helper: does [print ["Inside helper, dot is" .]]
        print ["Inside obj, dot is" .]
        helper
    ]
 ]

That HELPER shouldn't be giving a . is not defined error, just because you didn't call it as obj/helper or ./helper. And the non-qualified call to HELPER shouldn't try to implicitly pick up OBJ. It's simply not a method... and should use whatever definition of . that's in effect.

This means ./foo is a normal PATH!, and is the way to call other methods within the same object. But a special meaning is given to the "blank-headed TUPLE!" syntax like .bar to act as a convenient member selection shorthand, for being the same as if you had written (.).bar

Do you think it might make more sense to have a separate method type from function...where if you say obj.m for a method you'd get the binding implicitly, but obj.f for plain functions would not? Perhaps also obj.f() would not get a this, but obj.m() would?

This is what Ren-C does, and it makes the process of dot (or slash!) selection the actual arbiter of whether a "binding" (coupling) happens.

Short answer: yes—making “method” a distinct callable that carries its receiver is a coherent design, and several languages already do something close. It would move the decision “does a receiver get bound?” from the call site to the dot selection (or to the member’s declared kind), exactly as you suggest.

Here’s a quick tour of how this plays out and the trade-offs.

Precedent (this isn’t a wild idea)

  • Python: obj.m returns a bound method object that remembers obj; type(obj).m is the plain function (unbound). Extracting f = obj.m and calling f() still uses obj as self.
  • Go: x.M yields a method value (a closure with receiver bound). T.M is a method expression that returns a function expecting the receiver explicitly.
  • C#/Java method references: Creating a delegate from an instance method captures the target object (receiver) in the delegate.
  • JS (today) vs. proposals: JS treats “methods” as plain functions in properties; the call site decides this. There was once a bind operator proposal (obj::fn) to make binding ergonomic, but it didn’t land. Arrow functions capture this lexically, but that’s different.

What your design implies

If you distinguish method from function:

  • obj.m (method) → returns a callable with this (receiver) pre-bound to obj.
  • obj.f (function) → returns just the function; no binding happens.
  • Calling:
    • obj.m() uses the captured obj.
    • obj.f() (if allowed) runs with no this (or requires an explicit receiver).
    • Extracting let g = obj.m; g() still uses obj (goodbye “lost this” bug).

That makes dot selection the arbiter: the member’s declared kind determines binding semantics, not the presence/absence of a dot at call time.

Benefits

  • Ergonomics & safety: The “lost this when you extract a method” footgun disappears.
  • Clearer types: You can model this as two callable kinds:
    • method T -> (Args) -> R (receiver-captured)
    • function (Args) -> R (no receiver)
      Type systems (e.g., TypeScript) already flirt with this via “this parameters”.
  • Predictability: obj.m is stable under extraction, partial application, event handlers, etc.

Costs / gotchas

  • Allocation & performance: Returning obj.m must produce a bound callable. If that’s a fresh object/closure each access, you risk allocation churn and GC pressure. (Python/Go rely on VM optimizations; you’d want caching or escape analysis.)
  • Identity semantics: Are two reads of obj.m ===-equal? If you memoize, they might be; if you allocate each time, they’re not. Either choice has surprises.
  • Retention/leaks: A bound method keeps its receiver alive. Accidental long-lived references can prevent GC of obj.
  • Property uniformity: Today, “property get” yields a data value with no hidden ambient state. Bound methods make some property reads produce callables with hidden captures, which complicates reflection and proxies.
  • Rebinding patterns: JS idioms like const f = obj.m; f.call(other) would no longer retarget. You’d need an explicit way to obtain the unbound function (e.g., Obj.prototype.m or a special operator).
  • Meta/reflective ops: Debuggers, proxies, and decorators need to know the declared kind (method vs function) to preserve semantics during wrapping.

Design knobs to make it practical

  • Dual access forms (like Go/Python):
    • obj.m → bound method (receiver captured).
    • TypeOf(obj).m or obj#.m → unbound function (explicit receiver required).
  • Different operators: Keep current ., add a distinct operator for “bind on read”, e.g. obj.&m (Rust-ish) or resurrect a lightweight obj::m bind form.
  • Declaration-level distinction: In class/object literals, mark members as method m(...) {} vs function f(...) {} (or decorators) so tooling/optimizers know which is which.
  • Type support: First-class “method” type that subtypes to (receiver, ...args) -> result where appropriate, plus explicit coercions unbound(m) ↔︎ bind(unbound, receiver).

How this maps to JS today (workarounds)

  • Explicit bind: const g = obj.m.bind(obj) (manual, allocates, but clear).
  • Arrow wrapper: const g = (...a) => obj.m(...a) (captures obj lexically).
  • Class fields capturing this: handler = () => { /* uses lexical this */ } for event handlers, avoiding the “lost this” problem.
  • TypeScript “this parameter”: You can declare function f(this: void, ...) to forbid accidental this, or type this explicitly for methods.

Bottom line

A separate method kind that binds the receiver at member access is a clean model with solid precedent (Python/Go). It simplifies everyday usage and eliminates a notorious source of bugs. The main trade-offs are runtime overhead and object-identity/GC subtleties, which you can mitigate with memoization and clear unbound-access syntax.

If you were designing a JS-like language today, I’d strongly consider it—ideally with both access forms (bound vs unbound) available and explicit in syntax, so library authors and hot paths can pick what’s right for them.

Ren-C Cells are the size of 4 platform pointers.

FRAME! (and its antiform, ACTION!) reserve one of those pointers for the coupling.

Hence, it doesn't really cost anything at all to "create" a coupled action. It's the same as an uncoupled action (modulo the GC cost of keeping the coupled thing alive).

Two ACTION!s with the same coupling will compare equal, but different couplings of the same action will compare unequal.

If you want to compare the uncoupled actions you'd have to do that explicitly:

(uncouple a1/) = (uncouple a2/)

Seems pretty sensible, and is the least of our problems.

It's a little weird that doing things like iteration would give you "modified" members.

However we are looking at other aspects of that, like with $ indicating you want for-each loops to project bindings from a block onto the elements as you iterate:

for-each $item block [...]

That similarly writes a slot in the Cell (the binding, not the coupling).

So "getting" already is having some of these "hidden" effects.

As mentioned, there is UNCOUPLE.

The idea right now is that the answers to COUPLING OF are something like:

  • null = coupling of a/ - the item shouldn't be coupled by dot/slash selection
  • blank? coupling of a/ - the item should be coupled, but hasn't been yet
  • any-context? coupling of a/ - the item has a coupling
1 Like