Class.New() is for creating
a new class. To create a new object of the more general kind, use
Object.New() or
{}.
In my opinion, the
correct way to instantiate a class is to use
static new() and accept the possibility of an exception in the case where
the class cannot be instantiated. A caller should be able to assume that
static new() always returns an object (but it doesn't have to be a newly constructed one). If you want different behaviour, you can use a different factory method or function that constructs the object manually (or calls
static new() only once it is certain the new instance can be constructed).
For example:
Code: Select all
Image(W, H, R) => (p := CreateImage(W,H,R)) && Image.New(p) ; where New(p) returns an object or throws.
It is also okay to dispose of pre-established convention and choose or invent a different one, but there are disadvantages, and I would not describe it as
correct.
If you want to construct an object (not
instantiate a class) without having
__New() called, you can do it the same way that it was done before classes were added to the language; i.e just do it.
Code: Select all
obj := {} ; Create an object.
obj.base := Cls.Prototype ; Do whatever you want with it.
obj2 := {base: Cls.Prototype} ; Or do it in one step.
The catch is that the object and its base must be of the same native type (Object, Array, Map, File, etc.). If you want to manually construct a custom array based on Array, for instance, you must start with
Array.New() rather than
{}. In that case,
__New() is only called if someone defined it on
Array.Prototype, which is unlikely.
Class.New() worked for you because currently instances of
Class and instances of
Object have the same native type.
There are two reasons for the restriction:
- Native type is currently determined by the chain of base objects. If you change the base object incorrectly (such as via NumPut), the built-in type checks will fail; there will be some built-in method-functions that can be applied to the object but will crash the program, and others that cannot be applied even though they would work.
- It is done this way to avoid reserving extra space for type information, or the increased code size of other methods I tested (such as packing the native type into otherwise unused bits). C++ runtime type information seems to be insufficient (while simultaneously having parts we don't need that would bloat the executable).
- Even if you could base an object of one type on an object of another type, only the generic Object functionality (and user-defined methods) would work. For instance, none of the Array functionality works on Array.Prototype itself, because it is natively just an Object.
There are a couple of small issues:
- To manually construct a "Prototype" for a sub-class of a specialized type, you must start with an instance of that type. For instance, {base: Array.Prototype} won't work, but you can use an actual Array ([] or Array.new()) as a prototype. By contrast, the class syntax constructs an Object but flags it as a prototype, so calling specialized methods directly will throw a type mismatch instead of crashing the program.
- The primitive value prototypes don't have any Object methods, so you have to apply them manually to define new members. (You may consider this the cost of messing with global predefined objects. )