Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

So how should it be done?


The way we do scoping now is to rely on the programming language which is basically the stack.

But we can implement our own scoping mechanisms based on the domain we are developing a program in.

For example, Eve mentioned that they were looking at a sort of "Excel" spreadsheet type visual language.

The scoping mechanism of spreadsheets are a sheet with row/column. So, I would create objects that are able to quickly pull from a row/column and push into a row/column. For example, somewhere in their hierarchy they had an algorithm that required access to a cursor.

Since it is a spreadsheet we can have our global active spreadsheet which, somewhere, contains the cursor. We know it is there because we put it there.

Somewhere deep in the bowels of the data hierarchy we have this line of code to get a cursor:

    var cursorCurrent = global.curSpread[34,65];
because our scoping mechanism is based on an excel spreadsheet and not the one "forced" on us by the programming language: usually a stack.

There is no need to push that current cursor down the function calling train and, in the end, it is probably faster because we are doing a simple cell lookup.


The cursors were long before we were thinking explicitly of spreadsheets. Our model now has no encapsulating scope, which is just like access to some global object like you're suggesting here.


Why not full on adopt dynamic scoping? This is what McCarthy did in the original LISP (some say it was an accident, but it has lots of advantages compared to lexical scoping). Global scope, in contrast, is like having no scope at all. There is also nothing really wrong with this if you can manage your namespace separately Subtext style, but the use cases are still different from dynamic scoping.


> if you can manage your namespace separately Subtext style

We do

> the use cases are still different from dynamic scoping.

What are the use cases? We so far haven't felt the need for any kind of scoping (although we admittedly have only written a few 'real' programs).

It's not clear what dynamic scope would even really mean in Eve - there is no control-flow stack to set the scope and no notion of computer-time to dictate when a nested scope exists.

A better analog might be ML functors. We could take a chunk of Eve code and parametrise it by the input/output tables. Then you could wire it into your dataflow graph in multiple places.


LISP being technically based on the lambda calculus doesn't have any control flow either, but it does have hierarchy defined by callee/caller relationships. If you have any kind of hierarchy at all in your program execution (even if its just rules being used to for other rules...), you can leverage that as a "scope."

Dynamic scoping is useful when you want to configure different executions with different behaviors without invasively passing down contexts to do that. There is a whole body of work on context-oriented programming which essentially leverages dynamic scoping to make code execution more adaptive without sacrificing viscosity.


> when you want to configure different executions with different behaviors without invasively passing down contexts

That is certainly something we want, but I don't know if scoping is definitely the right way to do it, especially since both our code and data are entirely flat at the moment. We will likely have some kind of tagging of tables/rules for organisation and those tags could be the unit of reuse. The mechanism I have in mind feels more like copy-and-paste (I want that chunk of code but with these changes) or ML functors than it does dynamic scoping. In particular, the ability to change the dataflow graph at runtime without recompilation prevents lots of important optimisations.


With ML functors, however, you are duplicating the code and managing separately; you could already do that for each scope (what I basically do in Glitch), or better yet, try and optimize duplication away (copy your data flow graph, but memoize and try to reuse).

Anyways, the right path to take will probably become apparent as you do more development...keeping it simple at first is a good idea.


> try and optimize duplication away (copy your data flow graph, but memoize and try to reuse)

We sort of do that by hand at the moment. Rather than parametrise code, we attach ids to the data that is pushed through it. For example, instead of having a reusable 'compile' function we have a dataflow that just compiles anything thats added to the code tables and anything that wants to compile pushes code in one end and looks for the matching id coming out the other end.

I'm not sure how far that will take us. It's certainly annoying when what you want is more like a function with a return value rather than a side effect. Still, we might be able to address that with a little sugar. We'll see.


> but it does have hierarchy defined by callee/caller relationships. If you have any kind of hierarchy at all in your program execution (even if its just rules being used to for other rules...), you can leverage that as a "scope."

This is so true. You can take advantage of having access to a "parent" to change context. It allows you to re-use your code by just accessing information from your parent. It does lead to some coupling (it limits what types of parents you can have) but it does let you re-use code.

And hay Sean!


Interesting, do you have any reference about origin of COP in relation to dynamic scoping ?


From the man himself (Robert Hirschfeld): http://www.jot.fm/issues/issue_2008_03/article4/


Ha, I remember going through this one a few years back actually. I didn't know any names at that time.


That's basically what we end up doing in the February section. It fixes the information passing problem but then leads to new problems with re-usability. You can't parametrise a function using only global scoping. So then we ended up with both a stack and this global table lookup thing. We tried resolving it with that cursor system so you could pass a cursor to the root if you trusted a function or pass a cursor to some walled-off tree if you want to isolate it.

Another reason we weren't really satisfied with any of the hierarchical models because they required making lots of choices about ordering in the hierarchy that weren't really relevant to the actual problem being solved.

Now that we've moved to relational datalog-like language we just don't have scoping at all. Every table has a unique id and ambiguity between names is resolved at edit-time (eg by choosing from an autocomplete or by dragging a reference directly). We don't have a solid plan for handling reuse but we haven't actually felt the pain yet because doing things collection-at-a-time removes a good chunk of the use cases for reuse.


> You can't parametrise a function using only global scoping. So then we ended up with both a stack and this global table lookup thing.

Exactly. So, just write your functions so they don't use parameters (expect for persistence of the program). In fact, you can bypass functions all together. It also makes it a lot easier define and visually represent behavior.

> Another reason we weren't really satisfied with any of the hierarchical models because they required making lots of choices about ordering in the hierarchy that weren't really relevant to the actual problem being solved.

Hmm. Interesting. Could you give some examples? I would think that the hierarchy would be built out organically as you build out your program. Unless the hierarchy is actually just data that the program manipulates. Then, I could see it as an issue. But if the hierarchy is your program it should just grow organically.

> We don't have a solid plan for handling reuse but we haven't actually felt the pain yet

From a hierarchical standpoint, re-use (I'm guessing code re-use) is really about being able to change context by moving or executing part of your hierarchy (since a program is just a hierarchical composition of objects) in another part of your hierarchy. This is a little more difficult but can be done implementing another scoping mechanism.


> So, just write your functions so they don't use parameters (expect for persistence of the program). In fact, you can bypass functions all together. It also makes it a lot easier define and visually represent behavior.

I don't really understand what you are proposing. Can you describe how you would write a simple program (say a todo list) without parametrised functions? It works for Eve because its collection/query -oriented rather than value-oriented - we can express operations on arbitrary-length data without parametrisation. Even so, we expect to need it as we start to write bigger programs.

> Hmm. Interesting. Could you give some examples?

A good parallel that I use a lot is with file-systems. Picture your grandmother trying to decide whether http://jokideo.com/wp-content/uploads/2013/05/Funny-cat-and-... belongs in the funny-cat folder or the funny-baby folder.

Similarly, I often end up stuck in functional languages trying to decide whether I should have a map from employers to lists of contractors or from contractors to lists of employers. If I choose the former and I later need to find over-employed contractors the code will be really awkward. If I choose both I have to write extra code to maintain the relationship.

The big innovation of relational databases was the idea that you just directly model the employer-contractor relationship and you can later add contractor->employer or employer->contractor indexes without having to change any of your code. That's the big appeal of http://en.wikipedia.org/wiki/Data_independence .

The more I write code in relational languages, the more I notice how much of my time in other languages is spent trying to figure out where to put stuff and how to get at it later. Huge swathes of advice on design and maintainability are just working around this problem that relational databases (mostly) nailed decades ago.


In my favourite homoiconic language, Racket (also in lisps, so it's surprising that OP didn't mention it) you'd typically accomplish it with parameterised dynamic binding. Think global variables, except the lifetime of the new value is limited to a body of code.

A concrete example is a function that writes something to stdout. If you want to output to a file instead, parameterise the function call by setting current-output-port to the handle of your file.


Dynamic scoping?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: