Chainable "inspect" method for arrays (Array.prototype.inspect)

Other languages implement a chainable "inspect" method on iterable types. For example, Rust implements this method on std::iter - see the documentation for more information on how this method works.

I propose that a similar method could be implemented for Arrays. It would accept a callback, which would be invoked with the array as the first parameter and would return the array unchanged. Since it returns the array, it can be chained with other array methods to ergonomically inspect intermediate states of an array as follows:

myArray
  .map(/* ... */)
  .inspect(arr => console.log(arr))
  .filter(/* ... */)
  .inspect(arr => console.log(arr))

Related: Pipeline tap operator `|:`

The tap operator being discussed there doesn't directly help with this use case, as its operator precedence would prevent it from being inserted in the middle of a dot-chain like that. This problem was discussed there as well, and I believe the curren solution is to just convert the dot-chain to a pipline chain, like this:

myArray
  |> %.map(/* ... */)
  |: console.log(%)
  |> %.filter(/* ... */)
  |: console.log(%);

Not ideal, but it is a step in a good direction.


The .inspect() functionality being proposed feels like a very general-purpose piece of functionality that we might like to use in the middle of any dot-chained sequence, so instead of having to add an new method to any object type that may end up inside a long-ish sequence of dot-chains, I think it would be preferable to find some other (probably syntactic) solution that would solve this problem in the general case.

Using that tap operator and converting dot-chains to pipeline chains is one option, as shown above.

Another option would be to piggyback off of a proposal like call this. If that proposal were to go in as-is, we would be allowed to do this:

function inspect(callback) {
  callback(this);
}

myArray
  .map(/* ... */)
  ->inspect(arr => console.log(arr))
  .filter(/* ... */)
  ->inspect(arr => console.log(arr));

Here, the "inspect" function is being grabbed from the local scope and used inside the dot-chain. The inspect function could be made into a built-in function, that could then be used within any arbitrary dot-chain sequence, without requiring an individual inspect method to be defined on every object that wishes to support this behavior.

Thanks for the feedback!

I do agree that a syntactic solution to this would solve the problem and has the advantage of also solving many other problems.

However, I think this feature should be considered independent of a syntactic solution to the problem for a few reasons:

  1. The proposed pipeline trap operator requires learning and refactoring existing code to take advantage of the new syntax. This is not ideal since 1) it’s not very beginner friendly, and 2) in the wild people often use the inspect method as a temporary debugging aid so would not want to refactor existing dot chains to a new syntax.
  2. The “call this” proposal looks much more promising. It still requires learning new syntax but to a lesser extent. However, your example includes a “function” declaration outside of the dot chain which would need to be made available wherever one wishes to inspect the chain. I guess this could be inlined (?) but would still be less conscience than an inspect method.
  3. Why require a syntactical solution for the problem when a simple conventional and battle tested solution exists?
  4. The syntactic proposals may never land.

Although I don’t think it solves this problem any better than the proposals already mentioned, the extensions and “::” operator proposal enables a similar approach to “call this”:

const ::inspect = function(cb) {
  cb(this)
  return this
}

myArr
  .map(/* … */)
  ::inspect((arr) => console.log(arr))
  .filter(/* … */)
  ::inspect((arr) => console.log(arr))

From my understanding, the call-this is sort of a simplified successor to the extension proposal. Either one or the other will go in, not both.

Because, IMO, the simple battle-tested solution is also an incomplete solution. Just because others did something in a particular way, doesn't mean everyone should - it's how many languages ended up with a broken switch syntax, because they all kept copy-pasting the "battle-tested" solution from previous languages.

This is true, but if, for example, the call-this proposal does land, then we'd have the tools to universally solve this problem (instead of solving it just for arrays). And, if we do universally solve the issue through syntax, then all-of-a-sudden, this array.prototype.inspect function will become yet-another-legacy-function that doesn't have any point in existing anymore. In this case, I think it would be good to let the existing call-this proposal play out a bit.

Yeah, so what I would envision, is that first the call-this proposal goes in, then a proposal to provide a global "inspect" function goes in. That way, one doesn't need to be defining and importing their own inspect function, they can just use the global one. This would circumvent the issue you're pointing out here.

This criticism is absolutely fair. The tap operator is somewhat relevant, but it does a pretty bad job at helping debug a dot-chain.