Is this a function or a list?
(rectangle id1 nil)
Actually, it’s a bit more convoluted than that. Lisp was one of the first (if not first) “homoiconic languages”.
Programs are stored as lists of atoms (and lists, and, …)
“Eval” (as in Read-Eval-Print-Loop) is used to impart meaning to the lists.
So, in actuality, “(rectangle id1 nil)” is just a list containing 3 atoms.
Atoms that look like symbols are hashed, for efficiency. The above list, internally, is a list of 3 hash-codes, e.g. *list(hash-code-for-rectangle,hash-code-for-id1,hash-code-for-nil*).
Lisps are also classified by their packaging strategy, e.g. Lisp2’s vs Lisp1’s. Lisp2’s have several name-spaces and allow the same symbol (hash-code) to be used in different contexts, while Lisp1’s have only one namespace into which all symbols are poured (and must be unique from one another). CL is a Lisp2.
Eval() attempts to interpret the first item of a list as a function. Eval accepts a list, the symbol LAMBDA and any other symbol as the first item. If the first item is a symbol, eval() digs through the properties of the symbol looking for a function thing, and when it finds such a property it applies the function thing to the rest of the list (the args).
Symbols, like rectangle, have “1st class” functions stored in them under their function properties.
Apply() never really gets to see a symbol, it only gets a 1st-class function and a list of args.
In JS, we might say something like {name: rectangle, value: xyz, func: function (…) { …}}. I.E. “rectangle” can have a value AND it can have a function. Which property gets used is determined by the context (and, by the interpretation of the context).
The “type” of “rectangle” is SYMBOL, and, things of type SYMBOL have several properties, including VALUE and FUNCTION.
In PROLOG we don’t need to declare a rectangle type. PROLOG (being one of the early languages, I think it was originally built in Lisp), uses a similar hashing trick.
rectangle(id1,nil).
is also a bunch of hash-codes, probably stored as a Lisp-like list (hash-code-of-rectangle hash-code-of-id hash-code-of-nil). The type of the above thing is “functor” (and “functor” is a lisp-like list).
Lisp lists and PROLOG functors are generalized data structures. In a strongly-typed language, we would define a 3-tuple — {relation, subject, object} — and, we would implement these kinds of 3-tuples in a more efficient way than generalized Lisp-like lists. [Elsewhere, I argue, that this concern for this kind of efficiency is mis-placed. It’s very 1960’s. In 2020, we can burn CPU power to construct data structures at run-time, instead of wasting our own (programmers') time and efficiency. PROLOG is good at constructing (by inference) data structures for us. Our end-goal is always to run queries against data. If we structure our data at compile-time, we end up wasting brain-power, if we let PROLOG infer our data structures, we end up wasting CPU power. Either way, we are wasting <something>. I believe that wasting CPU power is a better choice tham wasting brain-power.]
PROLOG stores functors in some data structure, Lisp stores lists in some data structures. With PROLOG, we let the engine decide on the shape of the data structure, whereas in Lisp (and Python, and JS, and etc.) we decide on the shape of the data structure and hard-code it into the program.
A.T.M. PROLOG is worse at guessing what the best data structure is than humans are. We can usually create data structures that are “better” (more efficient) than what PROLOG decides to use under-the-hood.
But, that’s what Assembler programmers said until GCC came along. At some point, automation catches up with the best manually-written code created by humans.
Take-aways:
(rectangle id1 nil) is just a list of atoms (hash-codes) in Lisp.
rectangle(id,nil) is just a “functor” of atoms (hash-codes) in PROLOG.
Both are sufficiently general data structures for “normalization”.
PROLOG (and Lisp) are not as good as today’s programmers, but automation techniques will catch up.
Let PROLOG do your work for you. Don’t worry about preemptive efficiency, let them (and their compilers) catch up with what you’re doing.
If you need to deliver something today, write a design using factbases, then hire an Optimization Engineer to fixup the inefficiencies (after using a profiler ; (unprofiled Rust code ain’t gonna be much help)). If you can’t afford to hire an Optimization Engineer (and/or you don’t know what that is), then do it all yourself, but, do yourself a favour and create the design in layers. The goals of Architecting and Engineering should be to write as little code as possible (e.g. using an SCN) and to maximize the time allotted to thinking-things-through. Implementation should use no thought-time at all - the measure of Implementors is how fast they can create code (keyboarding skill, etc.). Implementors should never have to stop and think about a Design. All Design thinking needs to punted back upwards to Architects and Engineers and whiteboards. (Implementors punt back to Engineers, Engineers either clarify the details or punt back to Architects for clarifications of the Design).