8 ms·
Note that (a usual) Forth (here I'm centering on ANS Forth) uses early binding for most code, rather than late binding as you sound like you're talking about. T
by dasyatidprime 6y ago
Note that (a usual) Forth (here I'm centering on ANS Forth) uses early binding for most code, rather than late binding as you sound like you're talking about. The dictionary is indeed available at runtime, but runtime includes compile-time!—which is how a lot of the metaprogramming works, by interleaving the two with no strict phase separation. So it's while you're defining a word that calls other words that the other words are looked up, and not while you're executing it. This has some convenient implications for redefinitions:
: foo ( something ) ;
: bar 2dup foo 1+ * ; \ or whatever
\ ... in something loaded later on ...
: foo ( other thing entirely ) ;
: xyzzy foo bar ; \ calls later foo, then earlier bar which calls earlier foo
… which means it's harder to run into problems with stepping on “internal” symbols even if you're using short names in the “same” dictionary. “Purely” interpreted Forth does happen at the top level, in which lookups happen just before execution, but the part of it that isn't being used for compilation tends to be small fragments to kick off execution or to enter commands interactively. (In colorForth, phase distinctions are made by color instead, but I believe it also does early-bound compilation.)
In (a usual) Lisp (here I'm centering on Common Lisp and Scheme), function and variable lookups default to late binding, but based on symbol objects rather than strings. The symbols themselves contain pointers to function objects and/or variable contents, so there's a level of indirection, but it's not a hash table lookup every time. (Indeed in CL you can have “uninterned” symbols which usually have a name but can't be looked up by that name.) Again the mapping from strings, that is, packages in CL, obarrays in Emacs Lisp, etc. generally remains available at runtime, so you can do (find-symbol "FOO"), but when processing definitions, the lookup from string to symbol happens at read time. Then in CL there's some restrictions on redefinitions (http://www.lispworks.com/documentation/HyperSpec/Body/03_bbc.htm http://www.lispworks.com/documentation/HyperSpec/Body/03_bbc...) that make it easier for compilers to do more localized early binding even between top-level definitions.
Traditional BASICs I vaguely recall being closer to “fully” interpreted than this, but I haven't studied those enough to say—and I wouldn't expect that to apply to later BASIC revivals after a certain point.
An interesting other example would be Lua, which uses late binding semantics for global and method lookups, and then (here I'm thinking of the mainline PUC implementation) makes them faster by interning all strings at the C level, in a way which isn't exposed to the Lua level. When all strings with the same contents have the same pointer, string equality is very cheap, which makes table lookups over the string objects cheap. But lexical locals are compiled into index lookups, which are even cheaper, and it's often considered good practice to write internal file-level definitions as locals, import external module tables as locals, etc. so in practice once again some of the late binding disappears along the way.
- peter_d_sherman 6y agoWhat you say is all true. However, the word "binding", in Computer Science, is higher on the abstraction totem pole than say, words like "Compile Time" and "Runtime" -- which are more easily understood to new people... When you add "early" and "late" to the word "binding", you are talking about things which are highly interpreter and compiler specific, that is, different compilers and interpreters which contain compilers and interpreters and bytecode compilers -- will all do things slightly different from one another... Now, I should have amended what I said by talking about the earliest versions of Forth and Lisp. The earliest versions of Forth and Lisp. In those versions, they would not have bound ("binded", early or late) functions the way say a C compiler does, that is, by sticking the address of a function into machine code -- effectively removing all table lookups at that point. No, those earliest versions of Forth and Lisp (and especially Forth) -- would have looked up (from a symbol table), the same symbol, each and every time that it was seen/invoked. Would that have been slower than hell, compared to what a compiler can do? Absolutely! But that is how the earliest (AKA, "simplest"/"least complex") versions of these programs would have operated... So, I stand corrected (as I said, all of what you've said is true, I acknowledge that) -- but the thing is, if you were teaching an introductory CS class, you wouldn't want to teach what you've said to new students on the first day, because many would not have the background necessary in compilers and interpreters to understand it... So we're not arguing, you are correct, but while you are correct, please understand that what I search for is the most general, most broadest, simplest, unifying principles (maybe you would call them "axioms" or "first truths") from which all else (including all that you've explained) can be derived. But all of that being said... you are correct!
- kragen 6y agoYou're still wrong in the case of Lisp. The first runnable versions were interpreted, but even the earliest interpreter didn't do symbol-table lookups from the textual name on every function call. The symbols ("atoms") were absolutely stuck into the parsed code as addresses, removing all table lookups at that point. (But the interpreter still had to walk the atom's property list to find a property that would allow it to invoke the atom as a function, either EXPR, FEXPR, SUBR, or FSUBR.) You're speculating without knowledge both about how early versions of Lisp worked and, I suspect, about how to teach an introductory CS class.