mathjs icon indicating copy to clipboard operation
mathjs copied to clipboard

document iterator for Matrix classes (since they allow short-circuit unlike `forEach()`)

Open gwhitney opened this issue 2 months ago • 4 comments

At the moment, the callback in a forEach() invocation is simply called and its return value is ignored. However, some operations one might execute using forEach(), especially those of the nature of searching for an entry with a desired property, could benefit from the ability to terminate the iteration over the elements early.

Therefore, I would suggest having some sentinel value that could be returned from a forEach callback that would mean the iteration should terminate. Some possibilities:

  • Any truthy value. Note this might break existing code, if there are callbacks out there that don't want to terminate, but happen to return a truthy value.
  • A special Symbol generated for the purpose. This would not break existing code.
  • A plain object {terminate: true}. This is unlikely to break existing code, and allows the possibility of specifying a return value as well. (The more-complicated version of this option would use a special Symbol in place of true as the value of the terminate key to make it impossible to break existing code.)

In addition, we could allow the forEach function to return a value based on the return value of the callback. Some possibilities:

  • The return value of the callback the last time it is called is returned from forEach.
  • The last time the callback returns a plain object with key return, the value of that key is returned.

If the last option in each of the bullets above were adopted, then a callback could return {terminate: true, return: 0} to stop the iteration and return 0 from the forEach call (for example). Note choosing the first option from each bullet point would allow forEach to terminate and return a truthy value, but there would be no way to terminate and return a falsy value.

gwhitney avatar Oct 17 '25 16:10 gwhitney

Oh, I realize now that one can simply iterate over a Matrix, say M, with for (let elt of M) {...}, which can be short-circuited. So there's really no need to change forEach. In fact, should either or both of the following happen:

  • Emphasize ordinary JavaScript iteration over matrices more in the documentation, with less attention to forEach?
  • ~~Or even deprecate forEach altogether?~~ As dvd101x points out below, Array has this despite the overlap with a for loop, so it should be left in Matrix.

gwhitney avatar Oct 21 '25 06:10 gwhitney

Hi @gwhitney

Compared to regular arrays in javascript, they include:

  • map and forEach method, both with no way to break (i think)
  • a [Symbol.iterator]() method that returns an iterator of the values.
  • a values() method that returns an iterator of the values
  • a keys() method that returns an iterator of the keys of each index
  • a entries() method that returns an iterator of the keys/value pairs of each index (as an array)

Currently a matrix in mathjs has:

  • map and forEach method, both with no way to break (i think)
  • a [Symbol.iterator]() method that returns a value, index pair as an object. Might not be documented.

Areas of oportunities I see are

  • document the matrix [Symbol.iterator]()
  • use of a for in the expression parser that could include break, continue. There was previously a design decision about for
  • use of iterators in the expression parser
  • to be consistent with Array:
    • change the [Symbol.iterator]() to create an iterator of values
    • include an iterator of values() and keys()
    • include an iterator entries() (but as an Array, not an object)

As many functions also work with arrays that are expected to be jagged heterogeneous maybe a new class with all of those iterators might be created.

Also might be usefull to include iterators of mixed types with broadcasting. broadcast(matrix, jaggedHeterogeneousArray) returning an iterator.

There is also the oportunity to join the iterators together to be reused accross the codebase, it is complex to work with N dimensions in an efficient way with iterators.

dvd101x avatar Oct 21 '25 15:10 dvd101x

  • a [Symbol.iterator]() method that returns a value, index pair as an object. Might not be documented.

Indeed, I just searched the documentation and I see no mention of for (let {value, index} of M) ... for matrices. That is a documentation bug, so reopening this as such. The other items you mention should be cooked up in a Discussion, if you'd like to open one, before becoming an actionable Issue. Thanks!

gwhitney avatar Oct 21 '25 18:10 gwhitney

The use of forEach is indeed limited, I regularly had the case too that you want to stop halfway iterating over the items.

Sounds good to improve the documentation explaining that you can iterate over a Matrix using a regular JavaScript for loop.

I indeed still would love to add support for loops to the expression parser, and if we implement that, we can indeed implement break and continue.

josdejong avatar Oct 22 '25 12:10 josdejong