The = macro and a bunch of others use 'do, which is what ultimately does the thunk-wrapping. (You can see this from the REPL if you use 'macex1 rather than 'macex.) So if the thunks bother you, redefine 'do:
(mac do lines
(if cdr.lines
`((fn () ,@lines))
car.lines))
I can't guarantee this won't mess something up, but I did a search of arc.arc and couldn't find anything that would break from this, and I suspect most Arc code is the same way. I abuse 'do a bit in my own code, using (do.foo a b c) to avoid accidentally macro-expanding foo, and even that works just as well either way.
The fact that Arc didn't already give 'do a special case like this surprised me at first too, but at least it makes arc.arc about a dozen tokens shorter. :-p
Thanks. You guys are great. I get the reasoning and a work-around! It's not that it bothers me. I'm working on a compiler (for Jarc) and wanted to make sure it's okay to optimize away the thunk.
If Jarc still has the macro semantics I mentioned at http://arclanguage.org/item?id=11621, it may not be okay. That's because usually a call to a thunk suppresses the expansion of expressions inside it:
Jarc>
(mac bar ()
(do (prn "expanding")
4))
#3(tagged mac #<procedure>)
Jarc>
(def foo ()
(bar)
(prn 1)
(and (bar)) ; expands through '(bar) to 4
(prn 2)
(idfn (bar))
(prn 3)
(do (bar))) ; expands to '((fn nil (bar)))
expanding
expanding
#<procedure>
Jarc> (foo)
1
2
expanding
3
expanding
4
Jarc> (foo)
1
2
expanding
3
expanding
4
Jarc>
As you can see, (bar) in a function body is expanded to 4 as the function is defined, even if it's the result of expanding (and (bar)), but (do (bar)) is only expanded to ((fn () (bar))), and expansion stops here because it's a function application. In regular Arc, 'ac compiles function applications by compiling all their subexpressions, but apparently this doesn't happen in Jarc.
Like I touched upon in the other thread, if you were to "fix" this, that would break some of Jarc's ability to use lexically bound macros. Specifically, it would make it impossible to shadow a global macro with a local binding ('cause it would be compiled too soon). Lexically bound macros would still be available to an extent, since another two of Jarc's differences are that 'eval uses the lexical environment and that macro applications which somehow miss being compiled, like (let x bar (x)), are called with their arguments unevaluated (like fexprs).
On the other other hand, this fexpr ability kinda begs for function applications not to be compiled in all cases, or else what the fexpr sees will probably be the compiled arguments rather than the original ones.
Anyway, hope this helps you figure things out. ^_^
Jarc wasn't designed to allow lexically bound macros, that is just an accident of the current (broken!) implementation.
Jarc originally had dynamic binding, which is why eval uses the lexical environment. I should have changed that when I ripped out the dynamic binding. I'll fix eval. I'd like Jarc to be consistent with arc3.1
Oh yeah, that does the trick. At one point I was considering doing that in my macros for hygiene, but then I realized I preferred hackability to hygiene so my code could be consistent with arc.arc, so I abandoned that idea... and somehow forgot you could even do that. XD So that should make the implementation of 'global a bit tidier--maybe even a bit less necessary.
making arc try to macroexpand lists in functional position, and then re-macroexpand if the result was a symbol
Yeah, I considered that, but as long as I could make namespaced macros work without resorting to a patch, I figured I should do that instead, since it made the code more flexible to whatever Arc implementations and patch configurations people wanted to use.
The 'type function (along with 'annotate) is what I considered the "obvious" type system, but I don't think of it as being extensible, at least in common practice. For instance, the result of 'testify is a special kind of function, but its type is just 'fn. The type of a queue is just 'cons. A 'setforms entry needs to return a special kind of value... but the type of that value is 'cons too. So while a multimethod system might be built up on Arc's tagged values, I don't know how useful that would be.
As for Jarc--which I haven't tried yet--sure, Arc strings acting as Java strings must be nifty. I suspect that makes Jarc Arc-to-Java calls work quite a bit like Groovy, with dynamic rather than static dispatch? Sounds like a plus to me.
Speaking of Jarc, I intend to test out the Lathe code on Jarc at some point, but for the time being I'm sure at least the multivals won't work, thanks to the way 'self-orderer-reducer (the reducer for 'order-contribs) uses continuation-based backtracking to do its permutation search.
I think the solution to the type problems is to change arc to use tags more pervasively. No reason to be faithful to PG's implementation. Queues should be tagged 'queue, and so on.
I just re-implemented 'self-orderer-reducer so that it doesn't use continuation-based backtracking. :D I wanted to do that sooner or later anyway, just so that I could port Lathe libraries over to continuation-free languages if and when I wanted to.
In order to do this, I ended up porting an iteration library I'd written from Groovy to Arc, and then the problem was still hairy enough that I had to write Groovy pseudocode for what I wanted and port that too. Not counting the libraries each version needs (amb.arc and iter.arc), the new version is more than twice the size of the old one, and I'm sure it has more than twice as many fiddly bugs I haven't found yet. But hey, no continuations.
I'll try it out on Jarc later today. There are other things like reliance on tail-call optimization that might get in the way, but I'm generally hopeful.
It turns out supporting Jarc is way more challenging than I expected, and I'm going to give up for now. I pushed what I had to a new branch--woo, first time branching!--and it incorporates workarounds for quite a few idiosyncrasies of Jarc:
- Jarc doesn't have re-invokable continuations. As I mentioned earlier, I'm not relying on those for most of Lathe right now.
- Jarc has no 'get, despite having '.a and '!a ssyntax that expand to 'get forms. So I defined 'get myself, complete with an extension of setforms to make (= ((get a) b) c) work the way it works in Arc 3 and 3.1.
- Jarc has no 'a&b ssyntax, and the implementation of 'andf returns t on success rather than passing on the return value of the last function. So I defined 'doandf and used that instead.
- Jarc has no 'assign; it's named 'set instead. So I defined an 'xassign macro that could expand to the correct special form on Jarc or otherwise.
- Jarc has no (let (first . rest) lst ...) destructuring. I stopped using that.
- Nested quasiquotes don't work the same way in Jarc. I stopped using them.
- Jarc's 'compose apparently isn't a metafn. The symptom is that if I'm using 'a:b ssyntax where a or b is the name of a macro or special form, the arguments are still evaluated. I stopped using 'a:b syntax in these cases, and that was painful, since it gave every '(my:foo a b c) form an additional set of parentheses. I should have defined another macro to save me from adding those parentheses, but it didn't cross my mind until now.
Then I came across these two quirks in macro expansion:
; In Jarc, macros in subexpressions of function bodies are not
; expanded when the functions are compiled; instead, they're expanded
; when the functions are called.
(mac foo () 1)
(def bar () (foo))
(mac foo () 3)
(let foo (annotate 'mac (fn () 4))
(bar)) ; results in 1 everywhere I've tested
(mac foo () 1)
(def bar () (idfn (foo)))
(mac foo () 3)
(let foo (annotate 'mac (fn () 4))
(bar)) ; results in 3 on Jarc, but results in 1 on Arc 2 and others
; In Jarc, the macro expansions delayed this way are expanded
; according to the macros bound in their lexical environment rather
; than those in the global environment. I presume that this is an
; intended feature and that the other quirk exists mainly to let this
; work.
(mac foo () 1)
(let foo (annotate 'mac (fn () 2))
(def bar () (foo)))
(mac foo () 3)
(let foo (annotate 'mac (fn () 4))
(bar)) ; results in 2 on Jarc, but results in 1 on Arc 2 and others
The first quirk means my namespace macros almost never work from inside functions. Suppose I write something like this:
(using-rels-as ut "utils.arc"
(def my.fn-something (x)
(- x 1))
(mac my.something args
`(,my!fn-something ,(apply ut.arg-counter args)))
)
This won't macro-expand my!fn-something or ut.arg-counter until the 'something macro is invoked, by which time 'my and 'ut might be bound to completely different values.
I tried following along with these quirks so that when Lathe was running on Jarc, 'my and 'ut would be bound lexically rather than globally... but then I discovered that 'setforms doesn't expand places based on their lexically bound macros. I think 'setforms must call 'macex from within its own lexical environment, thereby neglecting any macros scoped in the expression's lexical context. And since all of my own uses of 'macex suffer from exactly the same problem, I've finally run out of steam.
For now, I'm just going to work on something besides Jarc support unless somebody here has a better idea. ^_^
Wow. Thanks for the laundry list. Most of the missing things are probably because I haven't moved Jarc from arc2 to arc3 yet. That's why assign is still called set also. I do plan do this. Probably as soon as I get the compiler working.
The macro expansion was just an expedient. Toplevel macros in functions are expanded at closure-time because it was trivial to implement. I wasn't sure how to do general expansion of all macros without a compiler, but I realize now, it's fairly straight-forward (you just have to know the semantics of the special forms). So I can envision a fix for this now. I never realized that delaying macro expansion would change the semantics (due to different lexical environment at expansion time), that seems pretty bad. I should probably look at fixing this before finishing the compiler.
I've fixed the most egregious problem, that of macro expansion timing in Jarc 10, released today.
I do plan to address the rest of these issues also in the next week or so. I appreciate all the info on Jarc incompatibilities with Arc. I wasn't aware of all of them.
Part of the Arc philosophy is to implement the language in such a clear, concise way that it serves as its own specification--or at least, that any proper specification would be harder/longer to read than the implementation itself. If an implementation is fine for what you're doing, there's a very recent thread (http://arclanguage.org/item?id=11583) about getting started programming in Arc. (It's easier to discover recent discussion if you click "new" at the top of the page.)
If it's really a specification you need--maybe you want to write an implementation under a license not compatible with the Artistic License 2.0... or maybe you just like specifications :) --then I think your best bet is the documentation at http://files.arcfn.com/doc/. It isn't perfectly comprehensive and up-to-date, but it is very good, and importantly, it doesn't necessarily expect you to want to look at the source code of the Arc implementation.
I kept hoping to find out it was just an April Fool's joke, but I guess it isn't. >_<
The first thing I think of when I hear the name "Racket" is tennis, suggesting the language is but one frequently visited taking-off point in some larger system. That would be okay if it were more obvious how it was meant; for instance, maybe the Racket language is a taking-off point on the way to the other languages PLT Racket supports. One could also talk about parentheses as being like Pong paddles, but that's a stretch.
The second thing I think of is a loud, grating noise, suggesting the language is a pain to read. This may or may not be an accurate description of the Racket language--hello parentheses--but the fact that it's an aversive connotation suggests that the language eschews utility (like Brainf___). Then again, I suppose naming a language after an intentionally obnoxious noise is better than naming it after an unintentional speech impediment (Lisp).
The third thing I think of is organized crime. Now, that's in the same ballpark as "evil mastermind," which was the first thing I thought of when I heard of Scheme (the second thing being blueprints), and indeed that's the association they're shooting for. In the context of that association, there's even a vaguely useful meaning to it; in my mind, small groups and individual masterminds scheme, whereas large organizations and conglomerates racket.
The thing is, if any language deserves to be named after organized crime, I'd say it's either something like C# whose use rather exclusively benefits one closed platform (not that it would name itself that way), or something like Mono which provides one of the few alternatives to such a platform. Trying to equate "organized illegal activity" with education and with multiple-language techniques doesn't make nearly as much sense.
Oh, and finally, "Racket" is eerily close to "Rocket," which is actually sorta positive for me (as my moniker shows). It almost makes me wanna write a parody dialect or something. ^_^
Racket's a terrible name for PLT Scheme, but long live Racket. I do think it's great to change the implementation name to something else, what with Scheme's own identity being intentionally split.
I'm not very familiar with srv.arc and stuff yet, but it seems to me like your (runjob) thread is just outliving the lifetime of the socket-accept streams opened in srv.arc's handle-request-1. First of all, your defopl form registers its behavior in the srvops* table. Later, in handle-request-1, a socket is opened, and then handle-request-thread is called, which in turn calls the srvops* op, but in the context of a w/stdout to that socket. Your thread is created, your op returns, and handle-request-1 closes the socket streams. Then your thread continues to run, and I presume that it tries to display something, but that the output is directed to the output stream that was already closed.
I don't know your specific situation, but if this is a page the admin visits in order to trigger (runjob) on the server, then the output of runjob--if there is any--is probably for the benefit of the server logs rather than something intended to be shown on the "/doit" page itself, right? If so, then I bet you can just change (thread (runjob)) to (thread (w/stdout console-out* (runjob))) and add (= console-out* (stdout)) somewhere on the top level.
I actually looked at Rainbow just yesterday and noticed you doing this. XD Immediately I wondered whether you'd put a link up here, and here it is.
Anyway, I think this is a great use of Arc. Whenever I'm manipulating something that has a lot of patterns in it, my programmer senses tingle and I find myself wanting to abstract things away. That way, the project is smaller and easier to wrangle, and I can't make small mistakes writing out all the patterns myself. Music is a perfect example of this, as you say.
As someone who only knows sheet music well enough to count my way eventually to the right keys on the piano, I'd like to volunteer that s2/4/5 jumps off the page for me even better than the sheet music does, since I don't have sharps and flats to worry about. In fact, I think I'd be more confident playing that opening motif if I put the Arc version in front of me. XD Chances are I just don't know what I'm missing, but still I hope you don't let the text-only nature of Arc discourage you. ^_^
my programmer senses tingle and I find myself wanting to abstract things away - you hit the nail on the head there. What's more, I'm sure there's meaning in those patterns if we could only make them explicit - listening to music is sometimes listening to a conversation in a mostly-foreign language - words here and there that you recognize, you suspect that something meaningful is being communicated, but it's just beyond your grasp.
don't let the text-only nature of Arc discourage you - no chance, I'm too far gone for that.
This is something I've been secretly hoping for for quite a while. I mostly lurk around here, only occasionally having something to say, and that's because I like Arc but I don't use it. I make small experimental hacks, and I like pursuing language design toward a minimalistic, timeless goal which is as open to the programmer as possible... but I hack in Groovy, and the language concepts I think about are not well-suited to Arc (albeit even less well-suited to Groovy ^_^ ).
I haven't started any threads here yet. Until I have something immediate to contribute to Arc, I don't feel like I should, since I'd be distracting from the topic of Arc itself, a topic that's obscure enough as it is. If there were another place with a topic of "Experiments in Programming Languages" or something to that effect, I'd feel a lot more at home.
There's Lambda the Ultimate (lambda-the-ultimate.org). That's not so much for experiments in programming as it is for PL theory discussion - fairly heavy on the academics, but very good.
Oh yeah, Lambda the Ultimate. I've read a few threads there, and I should probably check it out more often, since I can learn a lot very quickly that way.
But yeah, it's not a place programmers come to talk about random general-purpose snippets of code they've made, the way this is. I'm sure there are lots of sites like that too, but I guess I like the balance between that and PL design which this community seems to pursue. ^^
You're totally right about the way it works (except for your example; the generated macro actually has (defvar-impl ,'foo) which is effectively the same), but being able to choose isn't the issue, I think.
If quasiquote didn't account for nesting, all we'd have to do is this:
To clarify a bit, the ,', idiom doesn't have the same effect in both cases. It essentially breaks out of nested quasiquotes and breaks into non-nested ones. This is thanks to the fact that in the nested case the inner unquote applies first, and in the non-nested case the outer unquote applies first.
We can even simulate this non-nested behavior right now by writing our quasiquotes within ssyntax, so as to hide the inner ones from the quasiquote compiler:
(And for what it's worth, this change should affect the generated macro by removing the extraneous ,' in (defvar-impl ,'foo).)
~~~
With this in mind, why do we use nested quasiquotes? I'm guessing it's because code is easier to edit this way. We get to wrap a quasiquote template in another template (or to reverse that process) without having to go through and edit every single unquote form. We only edit the unquotes that break out of the wrapping, and we'd have to edit those anyway.
(mac side-efn (x y)
; NOTE: This form is only intended to be used in the case where x and y are
; raw symbols, as they are in forms like (side-efn foo bar).
`(= ,x (- ,y ,x) ; temp = old y - old x
,y (- ,y ,x) ; new y = old x (calculated as old y - temp)
,x (+ ,y ,x))) ; new x = old y (calculated as old x + temp)
Should 'undo reverse just one of these assignments or all three? Should it make any difference if these assignments are three separate = forms within a (do ...) block?
A top-level undo could be nifty, but on the Arc top level even the macro environment gets to change from expression to expression, so it can be a bit different. To allow undo on a finer scale raises the question of just how to measure the previous transaction. How many assignments should be undone, and what if they're hidden away in a call to a function nobody likes to read? What about assignments for scopes whose dynamic extent has ended?
I'm not entirely sure. Now that I think about it, fine grained undo is really a different concept from global state restoration, and is commonly only done for variables that the programmer a) knows about and b) has in mind the place he would like to restore to.
This means that finer grained undo would be more accurately implemented with save and restore functions, as opposed to just an undo function.
The global undo should work the same was as previously stated, more like a reset, and going all the way back to the start point, which may or may not be the beginning of the w/undo block.
Maybe a better system would be two pairs of save and restore functions, one that works on individual symbols, and the other that redefines '= to store the old value in a table if it didn't already exist, so that reset could restore it.