Array 2D class methods

Array2D inherits the following class methods from Collection:

  • fill
  • fill2D
  • fill3D
  • fillND
  • newFrom
  • with

Regarding method fill2D:

  • A doc search only links to Collection & Array
  • Array2D help page lists it as an inherited class method
  • Array2D.fill2D(...) fails with the following post error:

The preceding error dump is for ERROR: 'add' should have been implemented by Array2D RECEIVER: Array2D[ [ nil ][ nil ][ nil ][ nil ][ nil ][ nil ][ nil ][ nil ] ]

This is the full code that produced the error message:

 (((
    Array2D.fill2D // to produce an 8X8 table of fundamental frequencies
    (
        8 , 8 , // rows & columns
         
        {   
            |o n| ( [ 27, 30, 33, 36, 40.5, 45, 48, 51 ] [ n ] ) * ( [ 1 ] ++ Array.geom(7, 2, 2) @ o ) 
        }  


    )
)))

I suppose it may be interesting that it still created an Array2D with 8 sub-arrays… each containing a single nil instance… typical new behavior would be 8 sub-arrays with 8 instances of nil each, or 64 in total.

If this should be an issue on github, then just let me know and I’ll post it there.

Here’s the full error message:

ERROR: ā€˜add’ should have been implemented by Array2D.
RECEIVER:
Instance of Array2D { (000002737AB68C08, gc=C4, fmt=00, flg=00, set=02)
instance variables [3]
rows : Integer 8
cols : Integer 1
array : instance of Array (000002737A508468, size=8, set=3)
}
CALL STACK:
MethodError:reportError
arg this =
Nil:handleError
arg this = nil
arg error =
Thread:handleError
arg this =
arg error =
Object:throw
arg this =
Object:subclassResponsibility
arg this =
arg method = Collection:add
< FunctionDef in Method Meta_Collection:fill2D >
arg col = 0
Integer:do
arg this = 8
arg function =
var i = 0
< FunctionDef in Method Meta_Collection:fill2D >
arg row = 0
var obj2 =
Integer:do
arg this = 8
arg function =
var i = 0
Meta_Collection:fill2D
arg this =
arg rows = 8
arg cols = 8
arg function =
var obj =
Interpreter:interpretPrintCmdLine
arg this =
var res = nil
var func =
var code = ā€œ(((
Array2D.fill2D
(ā€¦ā€
var doc = nil
var ideClass =
Process:interpretPrintCmdLine
arg this =
^^ The preceding error dump is for ERROR: ā€˜add’ should have been implemented by Array2D.
RECEIVER: Array2D[ [ nil ][ nil ][ nil ][ nil ][ nil ][ nil ][ nil ][ nil ] ]

The same for Collection.fill2D, though more understandably, as an ā€œabstract class.ā€

ERROR: ā€˜add’ should have been implemented by Collection.
RECEIVER: Collection[ ]

In case anyone is interested, here’s the solution to the 8x8 matrix I was trying to produce.

In this case, .with is actually an instance method… which is unique to Array2D.sc

~xy = Array2D (8, 8) .with ( [ 27, 30, 33, 36, 40.5, 45, 48, 51 ] *.x ( Array.geom(8, 1, 2) ) )

~yx = Array2D (8, 8) .with ( ~xy.array.sort )

Using .with (instance method) is the cleanest way to instantiate an Array2D in my opinion.

Back to the before, or the original post in this thread…I’ve encountered other situations where a doc page will list inherited methods for a certain class, and still return object doesn’t understand errors or similar.

If an object class can’t use a particular method, then… is there any reason for it to be listed in it’s class reference?

Thanks to everyone reading in advance.

I see what you’re saying, but at the same time – the help file recommends Array2D.new or Array2D.fromArray so I suppose these would be preferred over with.

If I had my way, Array2D would be deprecated and removed. It doesn’t do anything that can’t be done with arrays-of-arrays. Because of that, it’s seldom used and not well maintained, meaning that things you’d expect to work don’t actually work. So instead of making things easier, it mainly causes confusion. IMO, dump it.

19+ years, I have never used this class.

I suppose not, but that’s a massive review job, very unlikely to become a high priority for an already backlogged dev team. (Note here that it isn’t with that is not understood – it’s a downstream error. So the only way to catch all of these is to test every method vs every implementing class, comprehensively – hundreds if not thousands of hours. While you’re correct that it’s misleading in the help, the reality is that we just don’t have the person hours to do it.)

The easiest solution is to mark the method selector as a private method in the help, and perhaps add a shouldNotImplement for it in the class definition. It would help if some users who feel strongly about it could submit pull requests to clean these up bit by bit – this kind of ā€œlow hanging fruitā€ is an ideal way to start contributing.

hjh

Same here, IMO there’s no point in using Array2D than facing unexpected troubles.

I use Array2D A LOT. There’s no reason to deprecate it.

The beauty of Array2D is being able to:

~xy[x, y] // instead of ~xy[x][y]

And on the documentation issue, I’m not going to press it or push it to the repo… but I’m sure one could fashion a script that runs through every help method with tryPerform

This isn’t an Array2D issue, it’s across the language, or it’s documentation.

The devs are probably already aware… ideally, every class inheritance should be able to perform all it’s inherited class methods.

Until then, I’m content with everything and where’s it at… I’m sure the dev team rarely gets thanked enough for all the hard work.

Thanks @josh @MarcinP & everyone else, for making this whole thing possible.

The gripes are never without gratitude.

Fair enough. BTW with ordinary Arrays you can also write

~xy@x@y

which is even less characters than

~xy[x, y]

though maybe not better readable.
I’m wondering if the latter syntax could be enabled for nested arrays ?!

1 Like

Given the .fill2D method exists, I’d be content with that.

Since xy matrices are so common, Array2D exists purely as a convenience class… the standard Array class is probably kept more efficient this way.

This particular form of access syntax is achievable at any size or length with MultiLevelIdentityDictionary, or a subclass.

(((
    Array.fill3D
    ( 
        *   ( 4 ! 3 )   ++ 

        {
            |...x| l = ( Library.global.put( * ( x ++ rrand(1, 10) ) ) )
        }
    )
)))

l[0, 0, 0]
l[1, 3, 1]
l[3, 3, 3]

The syntax is: ~library.put(x, y, z, ~object)

The last item in the list will be the accessible object

Right, ArrayedCollection::at refers to the primitive _BasicAt

For the sake of procrastinating with cryptic extensions we could define a multi-at (but slice is already here too):

+ Array { mt { |...i| ^i.inject(this, _[_]) } }
a = [[1, 2], [3, 4]]

a.mt(0, 1)

-> 2

b.slice(0, 1)

-> 2



b = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]

b.mt(1, 1, 0)

-> 7

b.slice(1, 1, 0)

-> 7

Some problems: 1. You would need appropriate arguments for each of those calls, which means inspecting every method/use-case individually. This, not the actual testing, is the bulk of the time investment. 2. tryPerform wouldn’t actually catch the case of Array2D.with. tryPerform only checks whether the method exists or not; it doesn’t catch execution errors. So you would need try. 3. There is an outstanding bug with try used in a loop – a rather nasty one. Unfortunately nobody understands it so it’s unlikely to be fixed, probably not ever.

IMO a better approach is: If you see a bug in an inherited method, report it. That is: ā€œI’ve encountered other situations where a doc page will list inherited methods for a certain classā€¦ā€ is only a complaint but there’s no concrete path forward (except to suggest that someone else ought to code review). Why not make a note of these as you see them, and then open a github ticket for several of them at once? It’s a lot more likely to be addressed if it’s ā€œhey, these 3 inherited methods don’t work as I expectedā€ rather than ā€œyou all oughta recheck every inherited method.ā€

Julian is fond of saying he doesn’t like to have a distinction between users and developers, and here is where he’s exactly right: because if you think of yourself as just a user, then it’s always somebody else’s job to take care of issues, so then it’s not important to report concrete problems – but it is actually crucial to report them!

hjh

Collection is an abstract class… one you can’t use directly. It exists as interface for all of it’s subclasses, through method inheritance.

In my experience, classes such as Dictionary and Array2D are unable to implement the class methods that are listed in the documentation as being inherited from Collection.

@jamshark70 you’re right about tryPerform, I’ve was working on a script that checks the entire documentation for failed method inheritance… I hit the brick wall exactly as you described it, before I read the last reply, however… here is what I have for Collection:

(((
    c = Collection.allSubclasses;

    ~failPerform = List[];

    c.do{ |x| { x.fill2D(8, 8, { |...xy| xy } ) } .try { ~failPerform.add(x) } };

    ~failPerform.printAll.size // -> 26
    
)))

That’s 26 class entries documented as inheriting 6 class methods fill fill2D fill3D fillND newFrom & with, from Collection… or, 156 doc links that lead to ultimate dead ends in the language.

To me, that sounds like it must be an issue… unless I’m really missing something here … as I said in the original post, I’ll take care of it if it requires further action… I just don’t want to waste anyone’s time, if it’s simply a misunderstanding.

Here’s the script that does that same for all of Collection’s subclasses, across the entirety of Collection’s inheritance list… @jamshark70 again is correct… the problem is knowing the particular method arguments , and being able to separate the class methods from the instance methods.

Here’s all the code at once, so it’s easier to copy & paste etc…

(((
    c = Collection.allSubclasses;

    ~failPerform = List[] ;

    c.do{ |x| { x.fill2D(8, 8, { |...xy| xy } ) } .try { ~failPerform.add(x) } };

    ~failPerform.printAll.size // -> 26 
)))

(((
    Collection.collectMethods(d = [].asDict); 
    
    ~failPerform = List[] ;
    
    c.do{ |class| d.keysDo{ |method| { class.perform(method) } .try { ~failPerform.add(( class : method )) } } } ; 

    ~failPerform.printAll.size // 3399 
    
    // c.size -> 40
    // d.keys.size -> 99
    // 40 * 99 -> 3960
    
    // 3960 total checks minus 3399 fails makes 561 successes & 3399 fails
    // this, however, may be far from accurate...
)))

I think two things about it:

  1. Every improvement in the docs is a good thing, and not a waste of time. This falls into the category of ā€œpaying down technical debtā€ and it also provides ā€œGood First Issuesā€ for new contributors.

  2. Currently it isn’t safe to assume that methods listed under ā€œinherited methodsā€ are guaranteed to be supported. That’s not ideal, of course, and it is the kind of misunderstanding you’re talking about. That said, there’s not a strong reason to accept the current situation as if it were ideal.

I’d support efforts to improve the documentation here.

hjh

Thanks James… for always taking the time to work / aid us in sorting these things out.

https://github.com/supercollider/supercollider/issues/5531#issue-958722792