Most interesting. I wonder how the environment knows when to draw a circle. The visualization piece lives and dies by how much work it is to setup for more abstract code.
What's your personal background like? When I Google your name I mainly get a bunch of news articles about this Kickstarter. :-p (Edit: Oh, whoops. You have a bio on Kickstarter after all. http://www.kickstarter.com/profile/1712125778)
What would you say the difference is between "flow-based programming," "reactive programming," and "dataflow programming"? It sounds like you're making useful distinctions but that I just don't know what they are.
From my experience, the term "Dataflow programming" is an overarching concept for both Flow-Based Programming and Reactive Programming.
I use the term "Pure Dataflow Programming" in reference to the original idea put forth by Bert Sutherland's paper "The On-line Graphical Specification of Computer Procedures" and others. It is characterized by fine grain operations (small operations like add or subtract) and the assumption that dataflow concepts will be employed for all levels of an application.
Flow-Based Programming takes the Dataflow concepts but says that the nodes (aka components) should be programmed using our current, sequential, control-flow programming languages. The Dataflow graph is a layer above and it controls the execution of the nodes.
Reactive Programming is a newer term. I use it to mean Dataflow using the push model (nodes only execute when data is pushed to it). It is "reactive" to changing data.
There is a tower of babble in respect to Dataflow terminology because we haven't yet defined a common meaning for all the terms. These are my definitions for the terms but everyone seems to define things slightly different.
I will have a chapter in the book that explains all the terms and their common synonyms.
As Dataflow concepts are just now starting to come to the attention of the masses, we don't have any consensus on the best, or most common, Dataflow implementation architecture. So the book will cover all the tiny variations of Dataflow architectures and compare them to each other for their pros and cons.
If you're old enough to remember when OOP started to hit the front pages, you may remember the debate between prototype based OOP and class based OOP. For the most part, class based OOP is the most common model by far. Thus, books now only cover class based OOP. Unfortunately for me, Dataflow programming has not reached that maturity level yet so I try to cover all variations and let the reader decide what is best.
In Arc, my immediate approach would be to define 'push as a macro, but also to give it a procedure version that's just a little more verbose. Then we can extend the procedure version and leave the macro version alone.
Here's some working Arc 3.1 code, which calls the macro "my-push", rather than overwriting Arc's own 'push:
That said, it's okay to drop the ((fn () ...)) in this case, since the code inside doesn't refer to caller_scope.
---
"I'm still not convinced I fully understand why eval and fn each add an indirection to caller_scope ."
(For the moment, I'm going to overlook eval here.)
That's just how you chose to build it, right? Isn't the whole point of caller_scope that it's specific to the current call?
I think the main issue you're encountering is that you always use the same name, "caller_scope", resulting in shadowing. Kernel uses an explicit parameter, as in ($vau () env ...), but Wart doesn't. This isn't necessarily a problem, because you can do something like this to work around the shadowing when necessary:
(fn (x)
(let my_scope caller_scope
(fn (y)
...)))
If you actually want to call a variable "caller_scope" even though it's not the current caller scope, you might have trouble with that, but you do have a couple of options.
I think this code will evaluate the ... as though it's just inside the (fn (x) ...), so that it can see the correct binding of caller_scope:
Unfortunately, the ... can't see the binding of y.
If you tweak the language a bit, you could make the implicit caller_scope parameter get shadowed by the explicit parameters. That is, if a function is of the form (fn (caller_scope) ...), you could let it ignore its implicit caller_scope binding altogether. (Currently Wart treats this just like any other duplicate argument name, and it throws an error.) In that case, you could use this technique:
Unfortunately, the ... can see the binding of my_scope. You might be able to get around this with a gensym.
---
"I'm still not convinced I fully understand why eval and fn each add an indirection to caller_scope ."
(Now to tackle eval.)
Are you sure 'eval adds a layer of indirection? I think the only indirection you're encountering is caused by 'fn. I just tried it out in Wart to make sure:
The first ((fn () ...)) layer establishes a binding for caller_scope, but it's an empty environment. The second ((fn () ...)) layer establishes another binding for caller_scope, so now caller_scope is bound to a first-class environment with a single variable in it named "caller_scope".
Within this second layer, we get a true value for (caller_scope = caller_scope). This is just a sanity check.
We false value for (caller_scope = (eval 'caller_scope caller_scope)), presumably because we're comparing the two different environments we've established.
Finally, it seems we can successfully use ((fn () (eval '<foo> caller_scope)))) in place of <foo>, because although it introduces a new environment, it evals in the old one.
Here's another way we can verify that eval doesn't introduce its own binding for caller_scope:
((fn () (eval 'caller_scope caller_scope)))
020lookup.cc:28 no binding for caller_scope
=> nil
((fn () (eval '2 caller_scope)))
=> 2
This all seems reasonable to me.
---
"But I needed it to expand to this:"
let $old push
mac (push x s)
`(if (isa stack ,s)
(push ,x (rep ,s))
(,$old ,x ,s))
I think you almost got it there. What you really needed was this:
mac (push x s) :case `(isa stack ,s)
`(push ,x (rep ,s))
-->
let $old push
mac (push x s)
`(if ,`(isa stack ,s)
,`(push ,x (rep ,s))
(,$old ,x ,s))
The difference is the addition of two groups of ,` heiroglyphics.
In between the , and the ` you have "room to operate on the names before starting on the backquote":
mac (foo n) :case (let a .. `(isa foo-type ,a))
with (a ..
b ..)
`(bar ,a ,b)
-->
let $old foo
mac (foo n)
`(if ,(let a .. `(isa foo-type ,n))
,(with (a ..
b ..)
`(bar ,a ,b))
(,$old ,n))
Since this approach would work for Arc-style (compile time) macros, I think it's what you were really looking for. Your "final expansion" branches on run time information before generating the code, so it's specific to fexprs.
---
"But even if there isn't, I'm starting to question if lisp is indeed the hundred-year programming model. I'm starting to feel at the end of my tether, at the limits of what macroexpansion can cleanly support."
False alarm! I'm pretty sure this was just a case of being rusty at one's own programming language. ^_-
I've been playing with this for a few days, and I'm still stuck on how to modify this decl:
(def foo (a) `(list ,a))
so that:
arc> (foo 3)
(list 3)
instead returns (list ,3). How can you generate a literal unquote? Perhaps this is a bug in wart's core? Perhaps it's impossible to do if commas don't expand to unquote?
Sorry, that toy example is too small for me. I don't understand why this macro redefinition approach would lead to that snag (although it does look like a plausible snag to end up in ^_^ ). Could you give some more context?
---
"Perhaps it's impossible to do if commas don't expand to unquote ?"
From what you're saying, it sounds like you're using some custom data structures, but that you just haven't given yourself all the primitives you need to manipulate them.
First of all, I'm noticing your expansion is suspicious in a couple of ways.
The case itself should be a piece of generated code:
-# mac (foo x) :case nil
+# mac (foo x) :case `nil
- `(if nil
+ `(if ,`nil
I made this same change back in http://arclanguage.org/item?id=18050 where I said "What you really needed was this." It might have looked like I was only talking about the expansion result, but the usage syntax changes too.
Also, this seems to be unrelated to the issue you're having, but I just realized you need to be careful about variable scope. What if the original definition said "mac (foo x)" but the extension said "mac (foo y)"? I recommend putting in a form that binds the right variables:
(Careful though, because this (with ...) form will add a layer of indirection to caller_scope.)
---
To address the issue you're encountering, yes, every case will expand at every call site, even if there's no intuitive need for it to expand there. It can't branch on a decision that hasn't been made until run time, unless you're willing to use the full power of fexprs. (I assume you're trying to maintain a semblance of stage separation, or you wouldn't distinguish macros from functions.)
Keeping this stuff in mind, you can write your code so that it doesn't give you that error:
# Simplified expansion of:
# mac (foo x)
# `(+ ,x 1)
#
# mac (foo x) :case `nil
# if acons.x
# `(list ,car.x)
# `(err "I hope this code will never be reached.")
mac (foo x)
`(if ,(with (x x)
`nil)
,(with (x x)
(if (acons x)
`(list ,(car x))
`(err "I hope this code will never be reached.")))
,`(+ ,x 1))
"What if the original definition said "mac (foo x)" but the extension said "mac (foo y)"? I recommend putting in a form that binds the right variables"
Whoops, that (with ...) stuff isn't ideal because it leaves the outer variables in scope. That is, if the original definition has a parameter called "list", an extension will see that binding even if it uses a different name for the same parameter, so it might try to call the global 'list and be surprised.
I suppose I recommend doing this instead, where g123-args is a gensym:
mac (foo . g123-args)
`(if ,(apply (fn (x)
`nil)
g123-args)
,(apply (fn (x)
(if (acons x)
`(list ,(car x))
`(err "I hope this code will never be reached.")))
g123-args)
,(apply (fn (x)
`(+ ,x 1))
g123-args))
Er, and there's one more change I should probably make here so you can actually use this for what you're trying to do.
let g1-old foo
(mac (foo . g2-args)
`(if ,(apply (fn (x)
`nil)
g2-args)
,(apply (fn (x)
(if (acons x)
`(list ,(car x))
`(err "I hope this code will never be reached.")))
g2-args)
(,g1-old ,@g2-args)
; or in Arc, ",(apply rep.g1-old g2-args)"
)
Arguably, it improves upon Arc in many, many ways. :) Perhaps Arc's main advantage is that there are more people who use and maintain Arc. I'm sorry to say I've never used Nulan for anything, even though I'm suggesting it here.
When ClosureScript came out, it didn't support eval (and it still doesn't, I think!), so I was motivated to build an eval-capable version of Arc in the browser. Behold Rainbow.js:
It's a direct port of Rainbow (https://github.com/conanite/rainbow), but with a hand-rolled parser and a hand-rolled implementation of asynchronous stream I/O. Like Rainbow, it "compiles" Arc code to an intermediate representation and interprets that. Rainbow was already a fast Arc implementation (faster than official Arc!) so Rainbow.js is pretty speedy as well. I used to think it was even faster than Rainbow itself, but then I realized that was due to a bug. :-p
I haven't been motivated to use Rainbow.js myself, because it has many quirks: Some quirks of its own (e.g. no threads), some inherited from Rainbow (e.g. no bignums, and weird call/cc behavior during macroexpansion), and of course all the quirky features of Arc (e.g. ssyntax). It also has some quirks which are truly necessary to make it usable; for instance, its (load ...) implementation loads from the Web, because there is no filesystem. While this (load ...) fix makes some sense, it's not always clear to me what the best behavior would be. Instead of pondering these small design questions and imposing my own decisions on this project, I've been working on other language designs from the ground up instead.
By all means, see if you can get Rainbow.js to work. I don't have ideas to improve this project myself, but you're not the only one who's wanted to use something like this, so your ideas could could help those other people. :)
The -m option means to call the "main" function now. Just leave it out. It used to mean "--mute-banner", which suppressed the "Welcome to MzScheme" line.
Yes. Version 372 was the last version which supported set-car! and set-cdr! for regular cons cells, so Arc 3 was locked to that version. However, Arc 3.1 overcame this limitation by using pointer manipulation to mutate cons cells anyway. This would sometimes fail, but this Arc 3.1 bug has been fixed in Anarki and Arc/Nu.
The /install page on this forum still refers to Arc 3, so this version dependency is a frequent point of confusion.
Yup! I looked for it a bit, couldn't find it, tried to post the top-level website, got caught by the dup detector, and ended up posting the thread that brought it back to my attention :)