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