-1

In Javascript and many other programming languages we may pass functions as parameters to other functions. This is a common practice in functional programming.

We know that a wrapper is required to put a breakpoint there, to see its place in a stacktrace, to have better control of the parameters or to add logic before/after the call.

Are there other objective reasons not to pass an unwrapped function as a parameter?

myFunction1(x => myFunction2(x)) // wrapped
myFunction1(myFunction2) // unwrapped
Rivenfall
  • 1,189
  • 10
  • 15
  • Of course, when you want to put break points, then continue with the wrappers... And if you need a function reference for use with `removeEventListener`, then you'll have to assign your wrapper function to a variable or name it. Not sure what your question is here. – trincot Sep 09 '20 at 09:04
  • Please note that `e => myFunction(e)` is equivalent to `myFunction`. However, `e => myFunction([]) (e)` is not equivalent to `myFunction([])`, provided you mutate the array. –  Sep 09 '20 at 09:46
  • @trincot I guess it's wise to continue with wrappers then. Not convinced (yet?) by the idea of passing functions directly. – Rivenfall Sep 10 '20 at 09:20
  • Depends on your needs really. If you don't need to do anything else (like break points *before* the function is called, or passing an extra parameter,...) and you don't need specific this-binding, then passing the function reference looks nicer in my opinion, and you'll have one wrapping-level less. But again, this choice is really driven by your requirements. – trincot Sep 10 '20 at 09:28

3 Answers3

2

Asking "should" you use one technique or another is mostly off-limits in SO.

I'm going to answer a related question, though, of when you can and cannot use the technique and what (dis-)advantages there may be.

You can always write foo (x => bar (x)). You can't always write foo (bar). An example of why can be found in another Q & A. A common example of this from my experience is a recursive function with a second parameter which is defaulted in the initial call and passed on subsequent ones. Such a function cannot be successfully passed by simple reference to map, because map supplies additional parameters besides the expected value.

But that's the unusual case. If your function has no default parameters, there seem little use in the wrapper. One argument is simply that if you're going to replace foo (bar) with foo (x => bar (x)), why shouldn't you take it one step further and use foo (y => (x => bar (x)) (y)) or foo (z => (y => (x => bar (x)) (y)) (z)).

The wrappers work, but add nothing... except, as you point out as a place to hang a breakpoint.

This could be a legitimate case for you. I don't spend much time in debuggers these days, but when I do, I might occasionally temporarily add such a wrapper. But I remove them afterward. I find that clean code is of high importance, and unnecessary wrappers simply clutter things up. In code reviews, I run constantly against this pattern:

const foo = (...args) => {
    // do something with args
    return new Promise ((resolve, reject) => {
        bar (something).then(
            (a) => {
                resolve (a);
             }, (err) => {
                reject (err);
             }
        );
    });
}

Which I always need to point out can be much more cleanly written as:

const foo = (...args) => {
    // do something with args
    return new Promise ((resolve, reject) => {
        bar (something) .then (resolve, reject);
    });
}

Then I have to point out further that even this can be better written as

const foo = (...args) => {
    // do something with args
    return bar (something);
}

The point is that the function wrappers around resolve and reject are just clutter. So is the Promise wrapper around bar. It doesn't hurt the outcome, has only a minor impact on performance, but it offers little to balance out the clutter.

(And look, I tried to avoid opinion here, but really couldn't.)

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • Thanks for the answer. Not to start another debate but I would use async/await when possible. And then eslint rules can help to avoid clutter i.e. no-return-await or @typescript-eslint/no-unnecessary-condition... – Rivenfall Sep 14 '20 at 15:51
  • @Rivenfall: I'm mixed about `async/await` vs `Promise`. But in training, I always want to make sure that users understand what `Promises` are and how they work before I offer them `async`. Part of my issue is that I [don't really like Promises](https://medium.com/@avaq/broken-promises-2ae92780f33) at all, preferring Future/Task implementations. – Scott Sauyet Sep 14 '20 at 15:57
0

You don't need to wrap a function with an anonymous function to assign it as event handler. If the function should get the event object, this is fine.

window.addEventListener('click', myFunction)

In addition, wrapping the function with an anonymous function would prevent you from removing the event handler, because remove expects the same event handler that was added.

This will work:

window. removeEventListener('click', myFunction)

This will not:

window.removeEventListener('click', e => myFunction(e))

If you need to pass more parameters to to event handler, you'll need to curry it, and assign the returned function to a variable, so you can remove the handler:

const createEventHandler = param => e => {}

const eventHandler = createEventHandler(true)

window.addEventListener('click', eventHandler)
// ...
window.removeEventListener('click', eventHandler)
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • That event handlers rely on function object identity is one of the most spectacular flaws of JS/DOM. –  Sep 09 '20 at 09:49
  • Indeed. jQuery made life easier, and in most modern frameworks adding and removing events is done in a declarative way, and the framework handles the JS side. – Ori Drori Sep 09 '20 at 09:55
  • I like native because it's native. The EventTarget API is as simple as it can be. I guess I shouldn't have talked about events in a functional programming question because it's arguably object oriented. – Rivenfall Sep 10 '20 at 08:53
-1
call(e => foo(e))

is exactly the same as:

call(foo)

with just one level of unnecessary indirection ;)

This has nothing to do with functional programming or currying and I can't see how it can hinder your debugging experience.

You would typically wrap when you want to lock some parameters or the signatures are incompatible. e.g.,

call(a => foo(5, a))

But in cases like this, you may want to consider currying.

customcommander
  • 17,580
  • 5
  • 58
  • 84
  • I don't think it adds complexity to wrap the function since it's easy to search a code base with the function name and an open parenthesis to find callers. It does help the debugging experience because you can put breakpoints and have one more place in the call stack. Passing a function directly seems less compatible with tooling in general (including search) and seems easier to reason about. – Rivenfall Sep 10 '20 at 09:20
  • @Rivenfall I never said it added complexity, only that is simply unnecessary IMHO. As for the debugging experience I have fared as well without them so ‍♂️. – customcommander Sep 14 '20 at 17:32