I know I can do it with: (foreach x 'in seq) and use (is 2nd-arg 'in).
This is probably the most intuitive approach, but since foreach is a macro, you don't need to quote in.
arc> (mac foreach (var in expr . body)
`(each ,var ,expr
,@body))
#(tagged mac #<procedure: foreach>)
arc> (foreach x in '(a b c) ; in, not 'in
(prn x))
a
b
c
nil
My definition is silly because it doesn't do anything with the in arg. It doesn't even require that the arg be in!
arc> (foreach x fruitloops '(o o o)
(prn "yum"))
yum
yum
yum
nil
More interesting things are certainly possible, though. :)
I think the colon is his convention for within the definition, as a visual marker to distinguish keyword parameters from others. When you later call a function or macro defined as such, you won't need to prepend the colon to arg you're passing (similar to how mine was called in within the definition but could be fruitloops when I called it).
Edit: On second thought, probably does use the colon when calling the function as well.
The prettiness wasn't a concern; as programmers we're used to needing hyphens or underscores, to ignoring parentheses and focusing on indentation. The colon's like that.
Could you help me to better understand the metafn issue? Those (do:a b) examples... what should they compile to? Does what you're talking about affect semantics, or just readability and performance?
When Arc compiles (a:b c), it compiles ((compose a b) c). When it compiles ((compose a b) c), it compiles (a (b c)). So in Arc, (do:a b) and (do (a b)) are basically equivalent, and so are (a:do b) and (a (do b)), where 'do is just an example macro. Since 'do is a macro, the equivalence does involve semantics; your transformation of (a:do b) calls a function named "do", when that function may not actually be defined.
Anyway, I'm just trying to make sure I inform you of stuff you may have overlooked. Once you know what I'm talking about regarding 'compose, it's up to you to decide whether you actually want to preserve an (a:b c)-(a (b c)) equivalence. ^_^ I am quite a fan of it, 'cause it saves some nontrivial edits (adding a closing parenthesis in a distant place and reindenting), but it's not sacred or anything.
Ah, I think I understand now. I had seen this comment from arc.arc:
; Composes in functional position are transformed away by ac.
And your examples show how 'compose gets transformed away, but I was having trouble visualizing a case where that wouldn't happen. Now it seems obvious to me: if you have (compose a b), rather than ((compose a b) c), then you can't transform compose away, because something is needed to make a and b act like one function.
Ack, there's something missing from that implementation: Nested string handling.
This is working well:
"\\"
'\\';
If you use 'quote to compile a string without escapes, you're fine:
'"foo"
'\'foo\'';
If you do the same thing where the only things to escape are quotes, you're fine, 'cause you call 'js-q, which uses 'nest-lev:
'"'"
'\'\\\'\'';
However, with other escaped things you're in trouble:
'"\\"
'\'\\\'';
I suggest putting 'js-charesc where 'js-q is now and having it use 'nest-lev directly. You might not even need 'js-q, since it could just be (js-charesc #\').
Right. I'm going to use [] as makeshift quotes. The JavaScript ['\'\\\\\''] evaluates to ['\\'], which evaluates to [\]. The JavaScript ['\'\\\''] evaluates to ['\'], which evaluates to an error, 'cause it's an unterminated string.
> stuck because I don't have external libraries (smart time parser, sockets, mysql, etc.)
Well, I would consider html.arc, srv.arc, app.arc, strings.arc and even some of arc.arc to be libraries. And if you include Anarki's lib/ directory along with all the resources from places like http://awwx.ws/, http://github.com/rocketnia/lathe and this forum, I think we've got a sizable collection going. Granted a lot of it is scattered and not as developed as in some other languages, and we also don't have a standard module system yet. :P
> Is that what PG had in mind when he created Arc? To supply a minimal language but to be able to use external libraries in the underlying interpreter..?
You might be interested (if you haven't already read them) in what pg has written about arc at http://www.paulgraham.com/arc.html. My impression is he thinks it's too soon to get focused on libraries because he's still working on the core language. (http://arclanguage.org/item?id=12127 is a relevant exchange too.)
> a little bit more like I would use Django? I.e. a simple routing to functions and a built-in template engine instead of mixing arc code with html and javascript..?
Maybe you could elaborate on this a bit? If I understand correctly, then I'd respond that html.arc is not unlike Django's templating engine, and you're welcome to separate your code into different files if you prefer it that way. (You're just not forced to.)
> Thanks for your help, it's appreciated.
I hope this was of some help. Thanks for your post!
I forgot to mention macros. You won't be able to play with them on the demo page. In the current model, you define macros in Arc using js-mac and functions in Javascript using the compiler (with the (js `(def ...)) form). To clarify:
- Everything is written inside js.arc [1].
- js.arc has the axioms defined with defs and most of arc.arc's macros with js-mac;
- it also defops arc.js, which has most of arc.arc's functions defined in Javascript [2].
[1] http://evanrmurphy.com/js.arc, also defines some in-progress Javascript utilities (mostly DOM-manipulation) and the demo page itself toward the bottom.
- Harumph. Looking back, I notice my ssexpand-all doesn't strictly work right. Instead of considering the actual cases, I just special-cased quotes and quoted unquotes. Technically, these are wrong.
arc> (ssexpand-all '`(a:b:c d))
(quasiquote ((compose a b c) d))
arc> (ssexpand-all '(fn (x:y) x:y))
(fn ((compose x y)) (compose x y))
arc> (ssexpand-all '(assign x:y z))
(assign (compose x y) z)
ssexpand-all should have a similar shape to macex-all, though it's more complicated and probably doesn't help much.
Bah. It feels like I've written this code about a million times. Gross.
- I notice you wind up copy/pasting a lot of arc.arc definitions, particularly for macros. Couldn't it be more general? I.e., add a clause like
(isa (car s) 'mac) (js (macex1 s))
to js1. I figure I'd want any ol' macro to expand, just so I could reuse existing code. Plus, this gives you all those verbatim arc.arc definitions for free. But you'd still want js-macs, since there are certain macros that shouldn't "spill over". E.g., the non-verbatim arc.arc definitions need to be tweaked for Javascript, but shouldn't be globally overwritten. You could make js-macs take priority over regular macros by checking for them first in js1, and still reduce copy/paste work. Would anything break from this?
- Really, the point about macros expands into a broader one. My first inclination is to model the compiler on ac.scm; once that's working, you get arc.arc & libs.arc for free just by cranking the compiler over them. Keyword collisions could be mitigated as in ac.scm, which prepends underscores to variable names.
arc> x
Error: "reference to undefined identifier: _x"
But this "compiler-oriented" approach might not work well. There are the Javascript keywords that you want to use from Arc without them getting treated like variables (mainly the reserved words). It's interesting because this project isn't just an Arc-to-Javascript compiler, but also a DSL for Javascript in Arc. So there needs to be some sort of balance.
There are more "overlap-y" examples like while. It's implemented as a macro in arc.arc, but you'd probably want to compile it down to Javascript's while instead of tail-recursive function calls. Also, some features don't line up quite right, like first-class continuations. Though you could compile them into Javascript (that'd be cool!), a big use for them is simply
(catch
(while t
(throw)))
which in more idiomatic (and probably more efficient) Javascript would be a while/break. So it isn't just calls to something specific like while, but also entire patterns of code that overlap. In the limit, you have Arc compile to complex Javascript, but I don't know how well Javascript handles as a target language for some of the crazier features like continuations.
I'm curious if you've tried this approach at all and, if so, how it panned out (I've never tried anything like it myself, so I wouldn't know).
As I was going through copy-pasting and tweaking definitions from arc.arc, I thought several times that there should be a smart way to automate the process. (And it is true that a large portion of the defs and macs are verbatim.)
For a long time I didn't have js-mac and wasn't using js-def. Each macro got a handwritten js-<insert name> function and its own branch in js1's if; each function was handwritten in Javascript. [http://arclanguage.org/item?id=11918] I finally got smart enough to start using the partially bootstrapped compiler, and abstracting away the verbatim definitions should follow from that.
You could make js-macs take priority over regular macros by checking for them first in js1, and still reduce copy/paste work. Would anything break from this?
I do think that will be the way to do it, and it shouldn't break anything.
It's interesting because this project isn't just an Arc-to-Javascript compiler, but also a DSL for Javascript in Arc.
I knew this was true but hadn't yet found a way to articulate it. Thanks for the good choice of words.
There are more "overlap-y" examples like while. It's implemented as a macro in arc.arc, but you'd probably want to compile it down to Javascript's while instead of tail-recursive function calls.
Yes. Since resources are so precious in the browser and Javascript doesn't optimize tail calls, this will probably be an important refinement. As a bonus, it may make the compiled js more readable, since (while foo bar) would be generating something like while(foo){bar;}, rather than:
Also, some features don't line up quite right, like first-class continuations...
Your idea of compiling the common case to while/break sounds fine, but wouldn't try/catch be suitable as well? (I haven't made enough use of either first-class continuations or Javascript's try/catch to know yet.) There have been some cases I've dealt with already where the features didn't quite line up: rest/optional parms for functions, conses/lists vs. arrays and nil vs. false/null/undefined.
Please feel free to use arcscript. I only dumped that file into the repo because I had it lying around and thought someone might have use for it. You got around to completing a nice functional JS compiler implementation before I did and I'm glad you did. Feel free to upload on top of my file :)
A simple front-end to this compiler I've been working on. The main (and only?) deviation from arc syntax has to do with object/list refs versus function calls. Because of the difficulty I had disambiguating the (x y) form, for the moment each case gets its own form as follows.
(objref x y) -> x(y); ; verbose
; no succinct form for no quotes
(objref x 'y) -> x('y'); ; verbose quoted
(x `y) ; succint quoted
x.`y
List refs:
(listref x y) -> car(nthcdr(y,x)); ; verbose
(listref x 'y) -> car(nthcdr('y',x)); ; verbose quoted
; no succinct forms for listref
Suggestions on how to improve this situation are welcome. I greatly dislike the lack of short forms for listref and non-quoted objref. The only perk is that since Javascript arrays are objects and numbers don't mind being quoted, you get array access for free:
(objref (array 'a 'b) 0) -> ['a','b'][0];
((array 'a 'b) `0) ; same as above
(let a (array 'a 'b) -> (function(a){
a.`0) return a[0];
})['call'](this,['a','b']);
(.`0 (array 'a 'b)) -> (function(_){ ; using get ssyntax
return _[0];
})(['a','b']);
I'm not sure, but maybe you need to do something like
chmod +x racket
before it will work as an executable? You might also try the users@racket-lang.org mailing list, #racket on freenode.net, or one of the other resources at http://racket-lang.org/community.html to get input on this problem.