Console.log with return value

Hey,

recently I wrote some function pipelines and wanted to debug the output. Example:

props.value
->Array.filter(item => item.id !== id)
->Array.map(item => item.id)
->Array.join(",")
->props.onChange

Since I didn’t save the result in a variable and just wanted to print it to the console, I had two options:

  1. copy the whole block, replace in the first pipeline the last props.onChange with console.log
  2. remove the last props.onChange, save the result in a variable, call console.log and props.onChange separately.

Cumbersome.

What do you think about a Console.log, which prints the value and returns it?
(I found out that gleam lang introduced an echo function for the same purpose, so I chose the same name).

props.value
->Array.filter(item => item.id !== id)
->echo
->Array.map(item => item.id)
->echo
->Array.join(",")
->echo
->props.onChange

Could be a handy function in the @rescript/core library.
Console.echo, top level echo, Debug.logAndReturn, Console.logAndReturn
(if using something like logAndReturn we could also implement infoAndReturn, debugAndReturn etc.).

If you agree with me, I’m open to create a PR for this.

Cheers,
Daniel

3 Likes

Definitely an interesting idea. I like logAndReturn (+ functions for the other variants outside of log) best in terms of naming.

more generally, I’d be in favor of having a function that applies a side effect and returns its parameter, we could call it tap, I find it more flexible:

let tap = (x, f) => {
  f(x)
  x
}

let foo = "foo"->tap(Console.log)
4 Likes

definitely also very useful.

I think that many devs use console.log to quickly debug stuff. Although ->tap(Console.log) is shorter than ->Console.logAndReturn, I would prefer the latter one if used frequently.

What about to implement both?

What about implementing both?

Please choose one; there’s no need to define two new things that serve the same purpose.

I would prefer tap to maintain flexibility. I might want to use a logger or an OpenTelemetry wrapper instead.

Console.logAndReturn feels clunky. Console.log is designed as a binding, and we already have enough overloads as it is. Additionally, I don’t think we want users to request more XYZAndReturn helpers.

2 Likes

I’ve used a tap command with fp-ts pipes. It console logs and takes an optional label.

let tap = (x, f) => {
  f(x)
  x
}

let foo = "foo"->tap(~label="bar")

I’ve something similar,

let trace = (~label: string="", obj: 'a) => {
  Js.log2(label, obj)

  obj
}

I prefer this sort of helper to be in userspace. If we decided to add this as a built-in function, then we should introduce a Debug module in Core to add this helper. But really I think this should just be in userspace.

Thanks for all of your input.
Since the rescript core has a few ...with... functions, I thought this is the recommended way.

There are only bindings to native js functions in the Console module, so I agree, that this is not the right place to implement these functions (although there are exceptions in other modules).

With tap, we could have a function which works for Console.log, Console.info, … Probably the best option.

Will we find a consent? Or should we keep it in userspace?

1 Like

I’d be in favor of having tap in the stdlib, I think such a PR would have reasonable chances to get merged.

2 Likes