Proposal: Default object method

Jordan Harband ljharb at gmail.com
Mon Jan 28 05:35:16 UTC 2019


Something that can be invoked has a `[[Call]]` slot, and is `typeof`
"function".

Adding a Symbol that makes something callable would have a number of
effects - it would make `typeof` (one of the most robust operations in the
language) unsafe, because it would have to access the Symbol method, which
could be a throwing getter (or even one that just logs how many typeofs are
called on it). Additionally, it would mean any object could become
callable, and any function could be made *un* callable.

This seems like a pretty large change, solely to avoid "classes with a
single method", which arguably should just be a function in the first place.

On Sun, Jan 27, 2019 at 4:05 PM Brasten Sager <brasten at brasten.me> wrote:

> Apologies if this has been raised before. I was unable to locate anything
> similar.
>
> Any thoughts or ideas on this proposal would be appreciated!
>
> Original: https://gist.github.com/brasten/f87b9bb470973dd5ee9de0760f1c81c7
>
> -Brasten
>
>>
> # Proposal: Default object method #
>
> Objects w/ default method can be invoked like a function.
>
> ## Problem ##
>
> Objects that are well constrained (single responsibility)
> can tend to end up with a single method, or at least a single method
> that is important to most consumers. These methods tend to be named
> by either verbing the class name (eg. `UserCreator.create()`) or with
> some generic `handle` / `perform` / `doTheObviousThing`.
>
> Whatever the name, downstream consumers of the object end up coupled to
> two implementation details:
>
>    1) this thing-doer is an object and not a function
>    2) this thing-doer's doing method is called `X`
>
> ### Example ###
>
> Here we are going to create an object that can be used to
> create a user later. Note that downstream consumers will only
> care that this object does one thing: create a user. While it
> make have other methods eventually for use in some limited
> contexts, creating a user is its primary (and often sole-)
> responsibility.
>
> ```js
> class UserCreator {
>   constructor(repository) {
>     this.repository = repository;
>   }
>
>   create(name) {
>      return this.repository.createUser(name);
>   }
> }
>
> const userCreator = new UserCreator(userRepository);
> ```
>
> At this point, the `userCreator` is just a single-method object.
> It is useful for injecting into other objects that may need to
> create a user. But the fact that the `userCreator` is an object
> with a single useful method is an implementation detail to which
> consumers become coupled.
>
> ```js
>
> // Consumer of `userCreator`. Although this could itself be a
> // good example of a "UserCreator"-like object (due to `.handle()`).
> //
> class UserSignupHandler {
>   constructor(userCreator) {
>     this.userCreator = userCreator;
>   }
>
>   handle(userName) {
>     // UserSignupHandler is aware of ".create" when it really doesn't have
> to be.
>     //
>     return this.userCreator.create(userName);
>   }
> }
>
> const handler = new UserSignupHandler(userCreator);
> ```
>
> Notably, if we were to change the implementation of UserCreator later to
> be
> a pure function, we would have to change all consumers of UserCreator when
> conceptually it shouldn't be needed. There is still a thing-doer that has
> the same input/output.
>
>
> ## Proposed Solution ##
>
> An object instance can have a default method. This would allow an
> object to be "invoked" exactly like a function, hiding the implementation
> detail from consumers.
>
> Note that there are several ways to define how the default method is
> determined, and this proposal is less concerned with this aspect than with
> what it looks like to invoke the object. We will demonstrate an option
> here,
> but alternatives are welcome.
>
> ```js
> // This particular implementataion would use a Symbol.
> //
>
> class UserCreator {
>   constructor(repository) {
>     this.repository = repository;
>   }
>
>   [Symbol.apply](name) {
>      return this.repository.createUser(name);
>   }
> }
>
> const userCreator = new UserCreator(userRepository);
>
> class UserSignupHandler {
>   constructor(userCreator) {
>     // NOTE: at the consumer, it almost makes more sense to
>     // name these with action verbs, as is done here.
>     //
>     this.createUser = userCreator;
>   }
>
>   handle(userName) {
>     // UserSignupHandler is no longer coupled to the implementation
> details it doesn't need.
>     //
>     return this.createUser(userName);
>   }
> }
>
> const handler = new UserSignupHandler(userCreator);
> ```
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190127/d443f557/attachment.html>


More information about the es-discuss mailing list