What would it take to make `Pseq [1, 2, 3]` work?

So one can write collections omitting a set of parentheses, e.g. just

Set[1, 2, 3]

but

Pseq[1, 2, 3]

is an error:

ERROR: ListPattern (Pseq) requires a non-empty collection; received 3.

What’s the magic involved in making the first syntax work? Can be replicated for Pseq somehow? (One probably have to contend with the default args, i.e. repeats=1 when writing like that.) I’m mostly confused that Pseq doesn’t even seem to receive the first two args here (1 and 2).

(N.B. In contrast, things that take a function as arg can consistently drop the parentheses, e.g. Pfuncn{42} works, and assumes the default repeats of 1 from Pfuncn’s constructor.)

Pseq isn’t a set (or a collection) - therefore you would need a different kind of interface. One idea would be creating an extension to Array or Collection that creates a method called asPseq, that returns a Pseq.
Otherwise, this is a syntactical thing. The first issue is, Pseq takes more arguments than just the list, so you can’t really hijack the Array syntax to do what you want here.
Something like this might work in a class extension:

+ List {
    asPseq {arg repeats = 1, offset = 0;
	^Pseq(this, repeats, offset)
    }
}

Then you could do:

[1, 2, 3].asPseq

The explanation for this is that classname [ ... ] doesn’t use a normal constructor call, i.e. doesn’t call new directly. Instead it does

		| classname '[' arrayelems ']'
			{ $$ = (intptr_t)newPyrDynListNode((PyrParseNode*)$1, (PyrParseNode*)$3); }

And that seems to push the number of args in the array first on the stack

    if (mClassname) {
        compilePushVar((PyrParseNode*)this, slotRawSymbol(&((PyrSlotNode*)mClassname)->mSlot));
    } else {
        compilePushVar((PyrParseNode*)this, s_array);
    }

    // compileOpcode(opExtended, opPushSpecialValue);
    // compileByte(op_class_list);

    compilePushInt(numItems);

    compileOpcode(opSendSpecialMsg, 2);
    compileByte(opmNew);

So the ‘3’ is actually the size of [1, 2, 3]. But alas it seems the rest of args inaccessible in a normal new

Pseqa : Pseq { // just for testing arg passing

	*new { | ... args | args.postln; ^nil; }

}
Pseqa [11, 12, 13, 15] // posts [4]

Aside: since I noticed how {} block args are actually handled (they can only come after no-other or parens args, but not after bracket args), testing with my “dummy class”

Pseqa(23, 33){44}{55}
// posts [ 23, 33, a Function, a Function ]

And more obscurely, this is also okay with key’d args “out of order” followed by function slots:

Pfuncn(repeats: 3){42}.iter.all // -> [ 42, 42, 42 ]

I know, I said that already in my first post. By the way Events can’t be fully constructed with all the bells an whistles from the abbreviate form. parent and proto cannot be set that way. For that the compiler does

void PyrDynDictNode::compileCall(PyrSlot* result) {
    //...
    numItems = nodeListLength(mElems) >> 1;

    compilePushVar((PyrParseNode*)this, s_event);

    compilePushInt(numItems);
    compileByte(110); // push nil for proto
    compileByte(110); // push nil for parent
    compileByte(108); // push true for know
    compileOpcode(opSendSpecialMsg, 5);

That doesn’t stop the (foo: 1, bar: 2) notation from being quite a typing saver for Events…

There’s actually an extra feature for Events (not sure if it’s documented) that allows parent and proto (but not know) to be set via fields in the constructor:

e = (parent: (foo: 2), proto: (bar: 4)) // -> (  )
[e.foo, e.bar] // -> [ 2, 4 ]

(know: false).know // -> true

Well, it helps to read a method till its end

void PyrDynListNode::compileCall(PyrSlot* result) {

    //   ....

    inode = mElems;
    for (i = 0; i < numItems; ++i, inode = (PyrParseNode*)inode->mNext) {
        // if (compilingCmdLine) post("+ %d %d\n", i, gCompilingByteCodes->size);
        COMPILENODE(inode, &dummy, false);
        compileOpcode(opSendSpecialMsg, 2);
        compileByte(opmAdd);
    }

The arguments are sent by separate add calls. That explains why all Collections take only a size in their constructor! And that also explains why Pseq doesn’t work out of the box like that. ListPattern is unaware such subtleties; doesn’t implement an \add, etc. Soo…

+ ListPattern {

	*new { arg list, repeats=1;
		case {list.size > 0} {
			^super.new.list_(list).repeats_(repeats)
		}/**/{ list.isInteger } {
			^super.new.list_(Array(list)).repeats_(repeats)
		} {
			Error("ListPattern (" ++ this.name ++ ") requires a non-empty collection; received "
				++ list ++ ".").throw;
		}
	}

	add { arg item; list = list.add(item) }
}

It does work:

p = Pseq[1, 3, 7]
p.list // -> [ 1, 3, 7 ]

But I can see the potential for confusion:

Pseq(4).list // -> [  ]
Pseq[4].list // -> [ 4 ]

which is unavoidable (given how the PyrDynListNode works) and frankly bit me a couple of times for Set too, but there it’s more obvious because Set prints all of itself on the post window. (Of course, ListPatterns could be made to do that too.) But there’s still the issue when the Pseq is embedded in something else…

Potentially PyrDynListNode could be changed to push a “truly special” symbol ahead of the argument count. That would allow one to distinguish between such cases… Or it could be changed to simply call a different constructor, not new… and Collection could just forward that one to new.

Although a change to PyrDynListNode as I suggest above would be better, since Patterns are usually only useful once asStream is called on them, and since that method is not redefined by any ListPattern, we can give a delayed error when asStream is called:

+ ListPattern { 

	*new { arg list, repeats=1;
		case {list.size > 0} {
			^super.new.list_(list).repeats_(repeats)
			.addUniqueMethod(\expectsAdds, {0})
			// ^^ quick hack to add a "field"
		}/**/{ list.isInteger } {
			^super.new.list_(Array(list)).repeats_(repeats)
			.addUniqueMethod(\expectsAdds, {list})
		} {
			Error("ListPattern (" ++ this.name ++ ") requires a non-empty collection; received "
				++ list ++ ".").throw;
		}
	}

	add { arg item;
		var ea = this.expectsAdds - 1;
		this.addUniqueMethod(\expectsAdds, {ea});
		list = list.add(item);
	}

	asStream {
		if (this.expectsAdds > 0) {
			Error("ListPattern (" ++ this.class.name ++ ") was promised % more items."
				.format(this.expectsAdds)).throw;
		};
		^Routine({ arg inval; this.embedInStream(inval) });
	}
}

Now

Pseq(4).list // -> [  ]
// ^^ Still can't be helped at that point, but later/if:
Pseq(4).iter // ERROR: ListPattern (Pseq) was promised 4 more items.

Pseq[4].iter // ok