Skip to content
This repository has been archived by the owner on Jan 26, 2022. It is now read-only.

Debugging and tap callbacks #8

Closed
js-choi opened this issue Oct 4, 2021 · 4 comments
Closed

Debugging and tap callbacks #8

js-choi opened this issue Oct 4, 2021 · 4 comments
Labels
documentation Improvements or additions to documentation enhancement New feature or request help wanted Extra attention is needed

Comments

@js-choi
Copy link
Collaborator

js-choi commented Oct 4, 2021

@runarberg had a good idea in tc39/proposal-pipeline-operator#225 (comment).

Just to throw it out there. If debugging is this big of an issue, why hasn’t there been a proposal for say Function.aside:

.map(Function.aside(() => { debugger; }))

or in the above examples:

await Promise.resolve('intro.txt')
  .then(Deno.open)
  .then(Deno.readAll)
  .then(Function.aside((x) => { console.log(x); debugger; }))
  .then(data => new TextDecoder('utf-8').decode(data))
  .then(console.log)

Which is rougly:

Function.aside = (fn) => (x) => {
  fn(x)
  return x;
};

Prior arts include Rust’s inspect, RxJS’s tap. If this is common enough pattern, perhaps it should be included in the Function helpers proposal.

Debugging in callback-heavy code is a pain. This convenience function would be generally useful. Maybe this should be called Function.debug or something.

My off-work time is currently limited, but I plan to add a section for this sometime in the next few weeks.

Please feel free to comment with more precedents from other languages and libraries here.

@js-choi js-choi added documentation Improvements or additions to documentation enhancement New feature or request help wanted Extra attention is needed labels Oct 4, 2021
@samhh
Copy link

samhh commented Oct 5, 2021

This would be valuable and, being a function, it composes well.

I suspect the overwhelming majority of uses of this function will be for console.log calls and debugger, in which case we might consider also shipping helpers for those? At which point we'd offer similar functional debugging to Haskell's Debug.Trace.

Status quo:

.map(x => { debugger; return x })

Function.aside alone:

.map(Function.aside(() => { debugger }))

Specialised helpers (hopefully with better names!) built atop Function.aside:

.map(Function.asideDebug)

Avaq added a commit to Avaq/proposal-function-helpers that referenced this issue Oct 5, 2021
@Avaq
Copy link

Avaq commented Oct 5, 2021

Added this need, along with mentioned precedents, to the table in #6 the wiki.

@js-choi
Copy link
Collaborator Author

js-choi commented Oct 5, 2021

I figure that I’ll add both a Function.tap(f) (x => (f(x), x)) and a Function.debug (x => { debugger; return x; }) to the explainer later.
Thanks for all the precedents from other languages and libraries. Further precedents would be welcome.

@js-choi js-choi changed the title Debugging callback Debugging and tap callbacks Oct 5, 2021
@boonyasukd
Copy link

Not sure if OOP language precedents matter much here. But here goes nothing...

In Java 8, we have Stream Processing API, which introduces map(), flatMap(), etc., along with peek(). The name is self-explanatory, and it somewhat aligns with Stack.peek() which is quite a common data structure API in modern languages:

Stream.of("one", "two", "three", "four")
  .filter(e -> e.length() > 3)
  .peek(e -> out.println("Filtered value: " + e))
  .map(String::toUpperCase)
  .peek(e -> out.println("Mapped value: " + e))
  .collect(toList());

In Scala, back in 2018, besides introducing pipe(), they also introduced tap(), which I believe is in the same spirit as RxJS, RxPy, Rxrb variants. Though in the case of Scala, pipe() and tap() are at the same level of chaining (not nested under then()):

val x = 1.pipe(plus1)
         .pipe(double)
         .tap(res => println(s"DEBUG: x = $res"))
         .pipe(double)

In Kotlin..., besides reusing peek() from Java API above, there are also the followings:

  • Inside a Sequence, we use onEach():
listOf("01", "02", "03")
    .asSequence()
    .onEach { println(it) }
    .filter { it.endsWith("3") }
    .any()
  • Otherwise, when composing code linearization via scope functions, we use also():
// run() + also() if you're `this` lover, 
"16384"
    .run(String::toDouble)
    .run(::sqrt)
    .also(::println)
    .run(Double::toInt)

// let() + also() if you prefer `it` syntax
"16384"
    .let { it.toDouble() }
    .also { doSomethingImpure() }
    .let { sqrt(it) }
    .let { it.toInt() }
    .also { println("The end result is: $it") }

js-choi added a commit that referenced this issue Oct 19, 2021
Decided not to include a `debug` helper function `input => { debugger; return input; }` due to lack of library precedent and overlap with `tap`.
Closes #8.
js-choi added a commit that referenced this issue Oct 25, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants