A basic spitball proposal to have some type structured datatype that functions like object but for large datasets.
Problem
Representing a large quantity of items, e.g. records using the Active Record pattern, or individual elements in a UI. OBJECT! has been cited as being inefficient for this task on account of copying context for each instance. Indeed METHOD was introduced as a workaround for the heft involved making copies of FUNCTIONs withing OBJECTs.
I've long argued that this solution addresses the wrong problem—that METHOD is the preferred behaviour of functions within objects and that we need a better solution of replicating the logic without duplicating the context every time.
Proposal
Having spent some time recently swimming in JavaScript's object-based waters, I'd like to propose a superstructure CLASS that incorporates an OBJECT with appropriate logic, and a container component—primarily thinking BLOCKs/MAPs where a clone of the CLASS retains the same object.
I'll use the Active Record pattern as an example here where FULLNAME(-OF) is a function that concatenates NAME and SURNAME fields for a given record.
Workarounds
There are four conventional approaches of note here. First, our query:
users: select db [* from users]
-
Take the hit: create 1,000 objects, each with clones of the record logic in addition to the data containers:
for-each user users [ probe user/fullname ]
This draws on the intuition of receiving things that have individual attributes.
-
Create a sub-object containing record logic that is not cloned with the rest of the object:
for-each user users [ probe user/actors/fullname user ]
This is somewhat more fudgy and requires two invocations of the object in one expression, at which point it may be better to use...
-
Create a single object that represents the table and use primitive containers for the records:
for-each user users [ probe users-actor/fullname-of user ]
One drawback is that the object actors can't share the same name as the result set which would be a likely clash. There's no certainty the primitives are items within the collection
-
Create a single object that represents a result set with per-table logic:
users/each [ ; let's assume that there's some way to establish 'user as ; the singular and that loop bodies are bound to the object probe fullname-of user ]
There may be merit to this approach, though some of the more generic methods would have be contextualized to be intuitive
Given those approaches, I'd suggest 1. is the more desirable and is worth exploring remedies to the inefficiencies.
Classes
As alluded to, a class would have a logic component (OBJECT) and a data component (BLOCK/MAP, though I suppose it could be any value):
; shorthand for: make class! [spec body]
users-record: class [record [map!]][
fullname: does [
spaced [record/name record/surname]
]
]
users: select db [* from users]
for-each user users [
probe user/fullname
]
To manually create an instance, you would do:
me: make users-record <MAP!>
; parameter dictated by CLASS spec, would be restricted to one value
Imprecise Mechanics
The question then becomes, how does USER/FULLNAME bind to RECORD, RECORD/NAME and RECORD/SURNAME in the class? I don't have all the answers here—this is something of a spitball.
I could see CLASS adapting generated functions to have an implied RECORD parameter. Path evaluation (the primary interface) on encountering a <CLASS>/<WORD> would—if <WORD> resolved to <FUNCTION>—pass <CLASS/RECORD> as the first parameter to <FUNCTION>. Of course, within the object, that'd make referencing other functions a bit tricky, but instead of binding in-object, you could do:
users-record: class [record [map!]][
fullname: does [
spaced [record/name record/surname]
]
hello: does [
unspaced ["Hello, " record/fullname "!"]
]
]
This may be a better purpose for the METHOD name where only METHODs are transformed for such a purpose:
users-record: class [record [map!]][
fullname: method [] [
spaced [record/name record/surname]
]
random-emoji: does [
first random ["😎" "🙂" "😜" "😊"]
]
hello: method [] [
unspaced ["Hello, " record/fullname "! " random-emoji]
]
]
I genuinely don't know if this would more efficient overall, but I suspect as binding would only occur on an as-needed function-invocation basis, it would be.
Whither Objects?
The main quibble I have here is what is the actual point of an Object? Modules have replaced Objects as isolated contexts for consolidating domain-specific code; Maps are perhaps better containers for settings (such as the SYSTEM and sub-objects). That just leaves their role as representatives of items of a collection, which as has been stated they are inefficient for this purpose. What other purpose do they serve?