Login [Register]
Don't have an account? Register now to chat, post, use our tools, and much more.
elfprince13 wrote:
suggestion: treat objects as compile-time hash-tables on symbols that flatten into structs in the runtime environment? This way methods and member variables share a single namespace ...


... runtime hashtables ... have numerous downsides, including performance concerns and type safety.


Thanks, I'll consider this if I get to a point where my POC shows promise. For now, in using JavaScript because I get objects for free, and I can quickly see if an object model lends well to representing models more easily. If not, then I may add well bring on the S-expressions with some flavor of Scheme.

I'll worry about optimization later; though if there is something I can do about it in JavaScript (I don't control the engine), that's probably worth knowing. One thing I am doing (not sure if it's better or worse) is wrapping values in a {type: , value: } object so I don't have to deal with complicated type-lookup; week see if that pays off.

By the way, I'm implementing it so that everything is in the same namespace, which JavaScript supports without complaint. I'm trying to do it in a way that "namespace" means the current lexical scope chain by default, but can be ANY object chain that is specified.

Anyway, if I do implement this in a lower level language outside of the browser, I'll certainly cutout whatever OOP overhead I can, and use a tiny handful of structs instead of classes. I'll also definitely consider full support for symbols as interned string-objects. I know JavaScript just uses strings for everything.
Forget what I said as to whether I'll implement Scheme directly or indirectly or at all. The short version is that I'll implement my own, using Scheme as a guide. And I'll do it with objects instead of lists.

The longer version:

First off, "objects instead of lists" means something like this:

(move 5 10) becomes {op:move, x:5, y:10}
(+ 1 2 3) becomes {op:+, args:[1, 2, 3]}

(Note: I'm looking to provide direct manipulation of structure over parsing text as the primary means of coding, so ignore the textual display)

The "Eval" will lookup operations in a dictionary rather than having commands hard-coded into an if-else chain. This will even include "behind the scenes" work like saving / loading lexical scope, thus leaving the system open for modification.

I anticipate that the only "compiled" ops will be the ones for working with object properties (get, set, exists, delete), but even those (or their wrappers) will be visible within the same "root scope" object.

... Meh, that's about as good as I can do right now without a working example. I'll get some code together (eventually) to show for it.
I'll reiterate my suggestion that you start with structs rather than dictionaries, and implement dictionaries on top of them, because this gives you one more layer of abstraction which is modifiable at runtime and not an immutable component of your system. (a dictionary is essentially just a partial function, and can be implemented in a ton of different ways).
elfprince13 wrote:
I'll reiterate my suggestion that you start with structs rather than dictionaries, and implement dictionaries on top of them, because this gives you one more layer of abstraction which is modifiable at runtime and not an immutable component of your system. (a dictionary is essentially just a partial function, and can be implemented in a ton of different ways).


1. OK, I'll look into it if/when the time comes

2. JavaScript doesn't have structs. In the context of JavaScript, by "dictionary" I mean objects (POJOs)

3. Since you're firm about this, let's talk about it now:

... So are you taking about creating a struct fur each type of object / node? Because that requires predefinition which prevents creation of ad-hoc objects at runtime. Note that user code is never parsed or compiled; it is dynamically "cons'd" together at runtime.

Or are you talking about, for example, implementing ad-hoc objects as fixed-size arrays, and providing macros that convert member names into hard-coded array-indexes? That would allow ad-hoc creation and modification of "types" at runtime, but not of existing instances (well, maybe the are since tricks; but let's not get into that right now).

Also, I'm not interested in traditional OOP craziness. Foo(o) instead of o.Foo(). ... Maybe that's just the short version, but due now let's assume there is nothing that smells like a "class".

Also, I'd prefer an underlying representation that directly represents the intended structure / mental model, rather than rely on a transformative step to support it.

A huge part of my POC is that human models are directly represented. I am likely to do the REVERSE though, and create a visual display/interface for things that is better than the literal object-representation... though I guess that's functionally the same idea as macros, huh? The difference I'm highlighting is that what gets stored is already a good information model without further transformation.

... Anyway, back to what you were saying: Am I close? If not, then perhaps an example(code?) would help.

Thanks for your continued involvement Smile
Here's a quick stab at how I might implement a continuation-based Eval in JavaScript:

Each continuation is an object that contains references to: the parent continuation, the "next" continuation, the current lexical scope ("environment"), and the code-object to be evaluated.

The code-object contains an "op" property (a string) that specifies the operation to perform. (I can use a symbol instead of a string, but I'll worry about that later).

The Eval function takes a continuation as its only argument, which it evaluates by getting a function from "environment" whose name matches the code's "op" property, and then it passes the continuation into that function. It then determines the next continuation to execute, and repeats all over if one is found; otherwise it exits. The next continuation is retrieved from the firs of the following properties to exist: The "result" property of environment, the "next" property of the current continuation, the parent of the current continuation.

Each "operation" function contains its own implementation, and thus decides how (or whether) to evaluate its arguments (i.e. the other properties in the code-object).

A typical operation would create a new continuation with code to combine the evaluated arguments, and with its parent set to the parent of the current continuation; then a new continuation would be created for each argument to be evaluated, each containing the code to evaluate the argument, and a "next" reference to the continuation for the next argument (as applicable), and the parent set to the continuation that was created to combine the arguments. Then the continuation for the first argument is "returned" by setting it as the "result" of the current continuation's environment.

Perhaps one such operation would be named "call", and it would work as described above, and then combine the arguments by calling (or rather, by creating a continuation that calls) a given function with the arguments.

. . .

This obviously needs revision; but without getting hung up on implementation details and efficiency, I believe I've captured the essence.

Further notes:

It is important to NOT create a growing call-stack, and instead let the continuation chain(s) capture it all.

Instead of using the underlying implementation (Javascript) to "return" values, results would be set as properties on the outer lexical scope, so that all state is exchanged via environment objects.

(there's more, but I have to stop here)

Edit:I came up with the above after reading about Scheme's call-with-current-continuation; but after studying more, I find that continuation passing style (CPS) is more like replacing "returns" with explicit callback functions. I don't care that I'm not implementing that model. All I care about here is that the call-stack be replaced with an object tree that is exposed for direct manipulation, and that all points in execution (including intermediate operations, like computing the nth argument to a call) can be saved off and resumed in an ad-hoc manner.
I now realize that what I've described in the previous post is more complex that (what I later discovered to be) traditional Continuation Passing Style, and I should revise my approach to be more in line with that.

Three big observations about my sub-optimal approach:

1. Having to mess with (i.e. set values within) the caller's environment (aka lexical scope object) to indicate what to do next. Not that I'm trying to create a pure-functional system, but side-effecting the execution environment as part of the built in execution model seems wrong / is a bad smell.

2. There is no real need to create an execution "tree" that can be walked. I was setting the "parent" and "next" nodes of each continuation so that you could "return" or "back out" by walking up the parent-chain (without having to step back through "previous" nodes within the same horizontal of execution). Instead, this can be done by capturing the current continuation at the time and storing it off (or passing it San argument) to be re-entered later, rather than walking a tree to find it.

3. It turns out that what I was really trying to describe is similar to an interpreter that converts code from direct style into CPS before executing it; except that mine would not actually do the conversion, and instead executes the direct code by doing what the equivalent CPS code would do.

On a side note, here is a project that is has a lot of similarities to this one (though as stated many times, I'm more interested in laying the foundation for a dynamic system than desiging a particular dynamic language):

http://lisperator.net/pltut/
(continuing from the previous two posts)
I think I'll still implement a manual "call stack" of sorts, but moreso as a mechanism to avoid growing the actual javascript call-stack, rather than as some tree to be directly inspected and modified by the code being interpreted.

I'll provide something akin to "call with current continuation" (call-cc), and that should be sufficient for runtime manipulation of continuations (that lisperator page provides some good examples).

Rather than call each other in a recursive decent, the interpreter/eval functions will be called through an intermediary function, and return (to the intermediary) an object containing the function-to-call and the continuation-function (e.g. a callback function that receives the result from the target function). The intermediary then calls the function-to-call, passing the continuation-function as an argument. This way, each function in the "call chain" (of Eval) returns before the next function is called, thus eliminating the "call stack".
Incidentally, the LLVM people have a nice little tutorial on manual stack-management, which may be of interest to you: http://www.nondot.org/sabre/LLVMNotes/ExplicitlyManagedStackFrames.txt
elfprince13 wrote:
Incidentally, the LLVM people have a nice little tutorial on manual stack-management, which may be of interest to you: http://www.nondot.org/sabre/LLVMNotes/ExplicitlyManagedStackFrames.txt

Thanks for sharing. I'm still deciding how I'll implement mine, but examples like this help.

So far, I'm looking at something like this:

Code:
O.exec = function (code, env, cb) {
   // code <-- evaluate this
   // env  <-- in this context
   // O.exec(funcToCall, newEnv, function (r) { /* apply r to next thing */ }); <-- call a function with current callback
   // cb(r); <-- return a value
};


However, I'd like to implement this in terms of the "objects" of the system that is being interpreted; so it's something more like this:

Code:
O.exec = function (code, env, k) {
   // code <-- evaluate this
   // env  <-- in this context
   // O.exec(funcToCall, newEnv, {code,env,k}); <-- call a function with current callback
   // O.exec(k.code, k.env, k.k); <-- return a value ... by setting a value in k.environment first??
};


The thing is that if I use the "callback" method, then I'm relying on mechanisms of the underlying language (JavaScript callbacks) that are inaccessible to the system being interpreted. But perhaps the solution is to go ahead and do that anyway, and then when I re-write it in terms of it's own language (i.e. bootstrap it), it will come together somehow as I find the equivalent code to do the same thing in the language of the system.
Progress so far:


Code:

function newObject (obj) {
   var v = Object.create(null);
   for (var p in obj) {
      if (Object.hasOwnProperty.apply(obj, p)) {
         v[p] = obj[p];
      }
   }
   var o = Object.create(null);
   o.type = O.types.object;
   o.value = v;
   return o;
}

function run (func) {
   var r = func();
   while(r && r.func) {
      r = r.func.apply(null, r.args || []);
   }
}

var O = window.Objects = { 'external': window };

O.types = {};
O.types.type   = { type: null        , name: 'type'   }; O.isType   = function (o) { return O.typeof(o) === O.types.type;   };
O.types.object = { type: O.types.type, name: 'object' }; O.isObject = function (o) { return O.typeof(o) === O.types.object; };
O.types.string = { type: O.types.type, name: 'string' }; O.isString = function (o) { return O.typeof(o) === O.types.string; };
O.types.array  = { type: O.types.type, name: 'array'  }; O.isArray  = function (o) { return O.typeof(o) === O.types.array;  };
O.types.null   = { type: O.types.type, name: 'null'   }; O.isNull   = function (o) { return O.typeof(o) === O.types.null;   };
O.types.number = { type: O.types.type, name: 'number' }; O.isNumber = function (o) { return O.typeof(o) === O.types.number; };
O.types.native = { type: O.types.type, name: 'native' }; O.isNative = function (o) { return O.typeof(o) === O.types.native; };
O.types.type.type = O.types.type;

O.null = { type: O.types.null };

O.typeof = function (obj) { return (obj && obj.type) || O.types.null; };

O.exec = function (cb, code, env) {
   // code <-- evaluate this
   // env  <-- in this context
   // O.exec(funcToCall, newEnv, function (r) { /* apply r to next thing */ }); <-- call a function with current callback
   // cb(r); <-- return a value

   if (O.isNative(code)) {
      return { func: code.func, args: [cb, code, env] };
   }
   if (O.isObject(code) && code.op && env[code.op]) {
      return { func: env[code.op], args: [cb, code, env] };
   }
   return { func: cb, args: [ code ] };
};

O.call = function (cb, code, env) {
   return { func: O.exec, args: [cb, code, O.newObject({ parent: env })] };
};


I am aware that there are some inconsistencies. For example, not all objects are yet created/treated as instances of O.type.object.
I'm going to start this fresh on GitHub: https://github.com/d-cook/Objects
I've now uploaded the code so far to the aforementioned Github repository, along with commit comments that help explain the different pieces of code (I committed it piece by piece so that I could do so): https://github.com/d-cook/Objects/

NOTE (of awesomeness):
I've created a way to preview the POC directly from the GitHub page!
The ReadMe which displays on the main page contains a link, which opens a previous page, which programmatically fetches the POC javascript and embeds it into the page. Currently there is nothing to "see", unless you want to open the browser debugger (F12 in most browsers) and inspect "window.Objects" from the console, to see what's been created so far, and perhaps to run some test code into it ("it" is probably window.Objects.exec)
Implementation notes to self:

The exec (maybe I should rename it to eval) will not check for a bunch of predefined ops, but will instead let them all be defined as functions. This way, "syntax" can be added / removed in an ad-hoc manner.

Those ops will NOT be looked up in a predefined object, but in whatever "environment" object is passed in. This allows for context based syntax (DSLs). There will be a root environment though, with all the base operations defined in it.

The ops should be defined in terms of their arguments, rather than in terms of interpreter variables (environment and continuation). However, here should be a way to define ops that DO act on these vars, analogous to LISP macros (yeah yeah, sanitized ones). Perhaps these can live in a certain property of the environment.
Code Update!

I now have the core of my POC implemented (mainly the eval function, but there's not much else needed). Have a look at Objects.js to see what it's about Smile

There is obviously still some work to bootstrap it into properly using its own code & idioms, but the logic is all there.

I will rely strictly on the F12 debugger for testing / demoing, though an ad-hoc REPL can be invoked like this: invoke(O.eval, [<YourASTObject>, <YourEnv>||O], <YourCallback>).

Next Steps:

The next "layer" will be the addition of language features (lambda, call-cc, let, if, ops like +, -, *, etc.). These will all exist as auxiliary functions (i.e. they work without eval() even having to know about them), and can be added / modified / etc. on a per-context basis. Very Happy

The next layer after that is to bootstrap the syatem, i.e. recode it all in terms of the aforementioned language features.

The next layer after that is an interactive UI, which will void the need for a REPL or any parsing of "source code" from raw text (think BlueJ for Java).

After that, further use or modification of the system is entirely an interactive process (no more "source code" or bootsrapping). This includes modifying language features, modifying the core kernel and its underlying native JavaScript, or even modifying the interactive UI via the interactive UI.
What's the difference between the thing you're building, and Smalltalk? (Especially Squeak, because that's the only one I've ever used.) Seems that is exactly what you're building.
zhangxiaobao wrote:
What's the difference between the thing you're building, and Smalltalk? (Especially Squeak, because that's the only one I've ever used.) Seems that is exactly what you're building.


Great question!

I've never actually used it myself, though I've studied plenty of material from Alan Kay and his affiliates from Xerox PARC (etc.), and one thing that I can identify is that the SmallTalk system is still highly dependent on the SmallTalk language & syntax. It's the same for Dan Ingalls' "Lively Kernel" (find that on YouTube) with respect to JavaScript.

Kay himself got frustrated with how language-tied SmallTalk became (find "The computer revolution hasn't happened yet" on YouTube). It was originally about the kind of system that can be made, with SmallTalk just being a vehicle to bootstrap it to begin with. Once it went commercial, SmallTalk itself (rather than the kind of system built on it) became the solidified artifact that came out of PARC.

I'm stepping away from syntax & language and working with informational trees from which arbitrary informational models can be created, and letting "syntax" be an artifact of however any tool decides to display that model.

My idea is actually somewhat similar to the works of Ian Piumarta (also from PARC), who tries to define a fluid model of programming language. He's also on YouTube.

There's also MPS by Jetbrains, but that's a whole huge framework with rigid tools, and is entirely focused on changing what it means to design software, and is still stuck in the world of programming languages rather than on redefining software & computers at a more fundamental level.

I recently discovered the works of Simonyi and his "Intentional Software" (find his hour-long YouTube video), and his concept of having an informational model at the middle with projections on top and generating binaries at the bottom, is eerily similar to my recent attempts to diagram the theory behind my model. Alas, his is focused on efficient production of software, though it sounds like it goes really far.

I've actually found STRIKING similarity between what I'm doing, and the Scheme language / interpreter. However, that's just the *means* to get where I'm going. Anyway, where I'm going requires that interpreter and all else to be in the same "soup" of modifiablility at runtime, so I must host my own; otherwise I may as well just grab Scheme or some other LISP and go from there. I think few people will ever really grasp what amazing possibilities are opened by LISPs, since they are only used as languages to write programs in, rather than as fundamental building blocks of a fully dynamic system.

One observation I have of other similar larger projects out there is the size, time, amount of framework, etc. that goes into them. I'm determined to show that such a system needs only very basic fundamental building blocks, so much so that it is something that can be bootstraped from almost nothing in various software contexts. The idea that you need a large framework that took years and much money to create, and works on a bunch of very specifically created pieces... that's a direct contradiction to what this kind of movement is about. I'm making a simple javascript POC because I think that once this technique / view of software, once sufficiently explored & demonstrated, is something that anyone can do anywhere, without needing big set-in-stone tools defining a complex process around it.
While reading an article about Forth meta-compilers, I've discovered what I'm implementing is a lot like Forth, though on the surface is still more like Scheme than anything.

Like Forth (and unlike Scheme), the whole system is defined/stored as named entries within dictionary-like objects, which can differ from context to context. Even the very interpreter/kernel that drives the whole thing is defined this way, and can be modified or cloned on the fly to modify the running system, or generate alternate systems from thin air at run time. This kind of open-endedness is at the heart of what I'm trying to achieve, and (as it turns out) the heart of what Forth is about as well.

However, the mechanism I am using to achieve it is essentially its own implementation of Scheme.

So I'm essentially combining the meta-ness of Forth with the well defined structure of Scheme. Pretty cool, huh? Smile
FYI, I have been looking at Racket, and paying more attention to its pattern matching and hygenic macros. I've also been comparing SICP and HTDP, and I found this really cool video about Racket DSLs!

("Learn you a Haskell for great good" has actually done a lot to help me understand the usefulness of pattern matching. I'm very excited that C# 7.0 has adopting a lot of that as well)

Anyway, it's all valuable stuff for anyone trying to do any kind of dynamic language or reinvent programming

... But I still maintain that I'm up to something a BIT different (e.g. instead of macros that transform code into code, you have GUIs that create editable VIEWs of code, in a bi-directional manner. ... That's not quite it either, but hopefully that helps paint enough of the right picture). Anyway, I'll see what I can learn/use from other languages and things.
Update:

I recently reviewed my eval function (which is the "brain" of the code / system), and upon finding inconsistencies and gaps, I sketched out the "grammar" of it, only to find problems with the intended grammar that it is meant to eval-uate.

So I reformulated it, and long story short(er), I've switched from objects to lists (JavaScript arrays) as the main building blocks of code. Amazing how hard it is to NOT reinvent LISP when creating this kind of system!

So I won't be fooled that that's not what I've got, but don't YOU be fooled that what I'm designing is about the language. It's more about having a language interpreter & runtime, and all associated tools (including the UI and the underlying machine representation) exist as modifiable artifacts within its own runtime environment, and then to extrapolate some representations and tools that are more direct and meaningful than looking at a vulgar textual representation of "code" in a essentially set-in-stone language ... and that's where this differs from SmallTalk. (Go back a few posts for more context on that point).

Anyway, (some form of) Lisp is a prerequisite for such a system to exist; but it's about the system, not the language.
It's been a while; any progress on the implementation side or is it still highly theoretical at the moment? Smile

I was looking through some of the recent changes to Objects.js, and I must say I am rather impressed Smile

I would also just like to note that the first stage 'Make an interpreter', format seems exactly like what the current system is for most programming languages. What really separates this from other languages? In the end the same steps have to be taken; I feel that languages are very limited by the hardware, not by the software implementation.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 3 of 5
» All times are GMT - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement