Pascal's answer is fairly good, but this touches on an important change and I think the mailing list should get a more comprehensive description:
J's arrays are immutable in normal use. When you write (2 (7}) a), J makes a new copy of (a), changes the value at index 2 to a 7, and returns that array, which is now unrelated to (a). There's no way to modify the original array using (}) alone. J has had an exception to this general rule for a while, in-place assignment. If you write a =: 2 (7}) a then J will modify the contents of (a) to replace index 2 with a 7. This is a mutable operation, and it changes other copies of (a) as well: if you executed (b =: a) before that line then the value of (b) would change. There's another way to get the speed of mutable operations in many cases without sacrificing J's immutable semantics, and this is what Henry is doing. Consider the snippet (2 (7}) 1+a). The operation (1+a) creates a new array, but this array is only ever used as an input to (}). Thus if we can identify these sorts of situations we can avoid copying (a) again for that operation, since we know it's safe to modify the array (1+a). Provided we never do this to an array that will be used again elsewhere, this means that we get a huge speed increase on the operations that can reuse their inputs. In principle, it's not hard to know when we can reuse an argument: J keeps track of a reference count for each array that tells it how many copies of it are in use. In the statement (2 (7}) a), the array (a) is referenced once in the symbol table (attached to the name "a"), and is referenced again when its value is copied as an argument to (7}). So its reference count is 2 when (7}) is executed. On the other hand, in (2 (7}) a+1), the array (a+1) does not appear in the symbol table, so its reference count is one. J keeps a reference count so it can free the memory for an array when its count hits zero, but we also know that if an array's count is one when it is passed to a verb, then it can be reused. Dyalog APL, K, and probably other APLs already do this. With J there are complications. In some cases a reference to an object is not counted, so arrays need a special signal to indicate that none of those cases apply. However, it seems that Henry has overcome these difficulties, and now J has some level of array reuse. The result for the user is just that some operations are much faster. J's semantics don't change, but its execution speed does. In many cases this can make a difference even to the asymptotic runtime of J programs, since something like in-place amend or append takes constant time but the same operation with a copy would have taken time proportional to the size of the argument array. Marshall On Sat, Oct 01, 2016 at 12:06:15PM +0200, Erling Hellenäs wrote: > Hi all ! > > As I am sure most people here know, functional programmers prefer to > use non-mutable data, very often lists. There are also non-mutable > arrays. Now, in J development, we talk about operations in place, > which seems to go in opposite direction, from the mainly non-mutable > arrays we have to mutable arrays. Is there an analysis which shows > that this is the right way to go? > > Another option would be to use non-mutable arrays, specifically > array classes structured in such a way that you can create a copy > which reuses the old unchanged content and then change the contents > of this copy. Usually you can create a mutable array and define its > content, then make it non-mutable before you return it. > > I tried non-mutable array in F#, and they seem slow compared to > ordinary arrays, but on some of them a copy operation was very fast. > I am not sure of how using such arrays would affect the performance > of our systems. > > Cheers, > > Erling Hellenäs > > ---------------------------------------------------------------------- > For information about J forums see http://www.jsoftware.com/forums.htm ---------------------------------------------------------------------- For information about J forums see http://www.jsoftware.com/forums.htm
