1. It specifically says "Append the second line "world" to the file." Not just print those two lines, but print the first line and then append the second line to the file.
2. Your code doesn't close the output port. This really isn't a problem in a basic example like this, but if you did it repeatedly...
arc> (repeat 4000 (w/stdout (outfile "fileio.txt") prn!hello prn!world))
Error: "open-output-file: cannot open output file: \"/[elided]/fileio.txt\" (Too many open files; errno=24)"
Most of the examples on that page do append and do close their files. So... here's my version, which I think is more correct.
(w/outfile f "fileio.txt"
(disp "hello\n" f))
(w/appendfile f "fileio.txt"
(disp "world\n" f))
(pr:cadr:readfile "fileio.txt")
Incidentally, "(w/stdout f prn!hello)" is just a little bit longer than "(disp "hello\n" f)".
1. It specifically says "Append the second line "world" to the file." Not just print those two lines, but print the first line and then append the second line to the file.
True, but then under Clarification, it says: "You don't need to reopen the text file after writing the first line." I'd thought that continuous write would pass for writing and then appending with the same file open, but I guess it doesn't.
The non-closed output port is a very good point though, and that disp makes it shorter is clever.
Cool, I have been attributed! :-) I feel I have to make another embarrassing nitpick, though, which is that we want the second line, rather than the second s-expression (they happen to be the same here, but the task is to demonstrate how to do these things). I would therefore go for one of these as the third piece of code:
(w/infile f "fileio.txt"
(pr:cadr:lines:allchars f))
; Or, though this doesn't close the input port:
(pr:cadr:lines:allchars:infile "fileio.txt")
; Both of the above, though fun, will read all characters
; in the file, only to return the second line. Better:
(w/infile f "fileio.txt"
readline.f
(prn readline.f))
4. Read the second line "world" into an input string.
5. Print the input string to the console.
Is an "input string" a variable? I guess it can't be much else... Ok. And I would put "prn.l" instead of "(prn l)"--not that it makes much difference, but I really like using ssyntax. Otherwise, I think I'm satisfied with this code.
And we've all written variations on this here, don't worry about calling it mine. It's a piece of code that we are collectively beating (or artfully crafting) into shape.
Never fear! I'll show up to flog this horse in the nick of time! Y'know, before rigor mortis sets in.
Here's another I/O utility I think would be useful.
(def readlines (n (o str (stdin)))
(let line nil
(repeat n (= line (readline str)))
line))
It undoubtedly sets a variable (just not a global), though I think the "challenger" phrased the requirement as such because of a C-centric view: allocate a chunk of memory for the string, then read the string into there (which, technically, even a simple (readline) does). Anyway... With all of these, the Arc code would look something like
And I don't think I could squeeze more out of that without getting overly specific. Of course, readlines is a conventional name for something that just reads all the lines of a stream, but I think we could reasonably use names closer to Arc's allchars and filechars.
On a side note, I notice you prefix certain things with "(do1 nil", presumably so that it returns nil rather than returning a gigantic string that the REPL will try to print in its entirety. I stick "no:" in front of things for the same purpose. I.e.:
I find this rather handy and thought I'd share it. (On another note, it seems "~" does the same thing as "no:", which I think is not much of an improvement (1 character instead of 3) and probably not worth allocating a whole ssyntax character for.)
You might find the Scheme/Racket functions "current-memory-use" and "collect-garbage" useful for this kind of thing. And perhaps interesting just to play with.
I put ($.collect-garbage) at the top of my defop and tested again. Memory leaks just the same. Somebody in Arcland is forgetting to take the trash out because Scheme has none to collect. :P
Nice macro, by the way. I added it to my utils with an attribution.
To make code show up here properly, put two spaces before each line of code and paste it here. As in:
;I used this function to add spaces so I could put it here!
(def add-spaces (xt)
(no:map [prn " " _] lines.xt))
This assumes you have it indented already. If you don't, find a text editor that indents Lisp code for you (I recommend DrRacket). The 'ppr function works reasonably well, too.
The sandbox library seems to work fairly well. Using the awesome quasiquote bug/feature that lets you drop to Scheme[0], I was able to evaluate (current-directory) --> "/root/arc/arc3.1/" and (version) --> "4.1.5", but the functions 'directory-list (like "ls"), 'open-output-file, and 'system don't do anything. Also, (current-directory <something>), which would change the current directory, doesn't do anything. That's probably somewhat reassuring to know, security-wise. (Someone has to try these things out, and it may as well be me, who intends to do no harm, before it's someone else, who might feel mischievous.)
By the way, an end-user can emulate stdout by doing something like this:
@waterhouse thank you for your eval-w/stdout function. I tried about twelve variations on the idea but ended up back at exactly the definition you gave. Even one cosmetic change that I was sure would be an improvement turned out to make it harder to read (at least in my opinion):
2. strings.arc, pprint.arc and html.arc are now all included in the sandbox:
arc> (plural 2 "dog")
"2 dogs"
; not indenting properly
arc> (ppr:macex1 '(accum a (each x '(1 2 3) (a x))))
(withs (gs954 nil
a
(fn (_) (push _ gs954)))
(each x (quote (1 2 3)) (a x))
(rev gs954))t
arc> (tag strong (link "Arc Forum" "http://arclanguage.org"))
<strong><a href="http://arclanguage.org">Arc Forum</a></strong>"</strong>"
At least for the moment I'm not including srv.arc, app.arc, code.arc and prompt.arc. Most of their functions couldn't be used in the sandbox anyway, and I can conserve resources (i.e. loading them in for each new user) by leaving them out.
arc> (def average (x y)
> (prn "my arguments were: " (list x y))
> (/ (+ x y) 2))
#<procedure: average>
arc> (average 100 200)
my arguments were: (100 200)
150
It simply checks for balanced parens. I just realized I neglected to check for balanced #\[ and #\] though, so for the moment your square-bracketed functions must be on one line.
Using the awesome quasiquote bug/feature that lets you drop to Scheme...
That is an awesome bug/feature.
To be sure, what you get at that repl isn't pure arc3.1 (at least for now it's not). It's arc3.1 plus anarki's $ minus libs.arc. So since it has $, you can drop to scheme without even exploiting rocketnia's discovery. You just can't do much harm there (at least, you're not supposed to be able to do much harm there, and you just helped me gain a bit of confidence about that).
It does concern me that you're able to see the current directory and version, but I guess as long as you're not able to change anything on the system it might be ok.
(Someone has to try these things out, and it may as well be me, who intends to do no harm, before it's someone else, who might feel mischievous.)
I completely agree. Thank you for trying to break it and reporting your results.
I'm guessing they defined afn because it was frequently useful, then defined rfn because they needed it "for use in macro expansions", and didn't find the refactoring worth their trouble.
Note, by the way, that rfn = "recursive fn" (that's the only reason you would use it instead of fn). But rif is certainly not "recursive if". If you still find it helps you remember it or that it makes more sense, sure, use it, but I would personally use iflet. (Or you could have it defined both ways and use whichever one your brain remembers first.)
Probably so that it can be changed more easily (by redefining 'make-br-fn rather than hacking the reader).
;arc.arc from Anarki
(mac make-br-fn (body) `(fn (_) ,body))
I recall at one point people talking about making brackets work so that, for example, [+ _x _y _z] = (fn (_x _y _z) (+ _x _y _z)). It would extract all arguments from the body beginning with _, alphabetize them, and insert them into the argument list. I don't know if Anarki actually did this, but I do believe it accepted [+ _1 _2 _3 ...] at one point.
I implemented that feature (multiple automatic parameters, with alphabetic ordering, so [cons _y _x] = (fn (_x _y) (cons _y _x))) in anarki. I don't know for sure, but I would assume it's still there.
In fact arc3.1 even works on Racket, the new PLT Scheme. Only thing is that the command-line "racket" prints a newline after the "arc>" prompts, for some reason. But you can open as.scm with the editor DrRacket (as you could with DrScheme), set the language to be "Pretty Big", and hit Run; it will work.
For some reason, now I don't notice any issues with the "arc>" prompt in "racket" either. And I don't think I'm doing anything differently than I was before. ...I am forced to conclude that, when entering things into the REPL, I held down the return key long enough that it accepted an extra (blank) line of input. This explains the behavior exactly. Strange that I should have done this several times in a row... and how embarrassing. Oh well. At least now I can give racket a clean bill of health.
That is a known issue with Windows. (I'm guessing it's the reason arc3 is still the "official" version on the install page.) Simple workaround[1]: Find the line that says:
Could you talk about your decision to use it for Readwarp then? If Arc's not really ready for production use, might it still be a good choice for a certain minority of developers?
Yeah, I'm not trying to say you shouldn't use it for production use :)
They're opposing perspectives. As a user of arc I'd throw it into production[1]. At the same time, from PG's perspective I'd want to be conservative about calling it production ready.
I suspect arc will never go out of 'alpha' no matter how mature it gets, just because PG and RTM will not enjoy having to provide support, or having to maintain compatibility.
[1] With some caveats: treat it as a white box, be prepared to hack on its innards, be prepared to dive into scheme and the FFI. And if you're saving state in flat files, be prepared for pain when going from 1 servers to 2.
The way ac.scm is currently implemented, it doesn't even matter if you rebind the symbol nil:
arc> (mz (set! _nil 'ach))
#<void>
arc> nil
nil
This is because nils are turned directly into 'nils:
(define (ac s env) ;ac.scm
(cond ((string? s) (ac-string s env))
((literal? s) s)
((eqv? s 'nil) (list 'quote 'nil))
Regarding numbers (and strings, you mention later): Well, why not allow '( to be a symbol as well? Because parentheses are special characters, and the reader interprets them as such. If you really want to make a symbol using special characters that the reader will recognize, you can escape the characters, using backslashes or vertical bars:
Numbers are special characters, in this way: if an entire token consists of numbers (or "<numbers>.<numbers>", and various other number formats), then the reader treats it as a literal number. But you can escape the numerical characters and bind the symbol 2 if you want:
arc> (= \2 3)
3
arc> (+ \2 4)
7
arc> '\2
|2|
Escaping non-special characters seems generally to do nothing:
arc> (is 'ach '\a\c\h '|ach|)
t
So, really, you can already bind the symbols 2, "meh", and so on. It's just that you need to escape them, or else the reader will interpret them as literal numbers and strings and so forth. (By the way, the Arc compiler still interprets the characters ~.&: as ssyntax even if you escape them. That is most likely a bug, rather than a conscious design decision.)
In light of the above, and aw's example of the expression (let nil 3 ...) vs (let () 3 ...), it seems like either the reader is treating nil as a special case and turning it into (), or the whole macro-expansion environment is treating the symbol 'nil specially (not the value of the symbol nil, but the symbol 'nil itself). In fact, we can see in ac.scm that the latter is the case. Either way, handling of 'nil happens before the value of the symbol even starts to matter. (And hey, that's basically what I said at the top!) It seems that if you want (= nil something-else) or (let nil something-else) to have any noticeable effect, then you can't have (let () ...) mean the same thing as (let nil ...) anymore. I don't like that option. I like having nil be a literal empty list.
However, I could see this being done: have nil be escapable, so that |nil| is the symbol you can rebind, while nil is the literal empty list. ...And then are they eq? If they were, then macros passing around |nil| would probably blithely turn it into nil. If not, then... then this official nil symbol is not the same thing as a symbol constructed from the characters n,i,l, which breaks my mental model of the universe. So I guess being able to say (let nil ...) just affords nil special status. (I suppose 'let could very well treat the symbol 'bob specially as well--but no, because let is really skin on top of fn, which is a fundamental part of the language.) I think I'm happy requiring that nil be un-rebindable.
Regarding t: I am all for allowing local rebinding of t. It's already possible to (let t 2 t), just not to follow it with (++ t), which seems pretty clearly to be a bug. I also agree with allowing global rebinding of t; it really is just a symbol, and even though it does show up in the primitives ('is and 'atom choose to return 't as their truth value), they could just return 't rather than t (i.e. quote the symbol), and then t wouldn't have to have a value at all. It is simply for convenience that we effectively have (= t 't). So, yes, t should be rebindable, and I have accordingly modified my ac.scm; just note that if you do rebind t, then everything under the sun is liable to stop working, though less so than if you rebind car:
arc> (= car nil)
nil
arc> (= a 2)
Error: "Function call on inappropriate object nil ((a 2))"
The escaping system is more robust than I realized. I think some of my concerns are definitely addressed with being able to assign escaped numerical characters, etc.
> However, I could see this being done: have nil be escapable, so that |nil| is the symbol you can rebind, while nil is the literal empty list. ...And then are they eq?
I looked into this and think it's kind of elegant now. 'nil is unescapable like non-special character symbols in general are [1]:
arc> (is 'nil '\n\i\l '|nil|) ; like your 'ach example
t
But () is escapable (as special characters typically are):
[1] I was frustrated momentarily when I couldn't escape 't until I realized it was the same way. As you pointed out, at least it is locally rebindable by default. And then I suppose 't isn't an especially desirable global variable name anyway.
It seems to me that a lexical binding should override a global macro binding, no matter what. I don't see a reason not to do it that way, except possibly that it would make it harder to handle macroexpansion (it adds another special case to macroexpansion, along with quoted/quasiquoted forms and parameter lists and wherever else macros shouldn't be expanded). But we already have a full code-walker in ac.scm, and aw has shown us just where to look and what to do, so this turns out not to be much of a problem.
And it turns out that both Common Lisp and Scheme agree with me, that lexical bindings should override global macro bindings. As we see here (pardon my choice of throwaway function name; it's short and the computer doesn't mind):
> (define-macro (ass x) ;Scheme
`(list ,x 2))
> (let ((ass (lambda (x) (+ x 1))))
(ass 1))
2
* (defmacro ass (x) `(list ,x 1)) ;CL
ASS
* (flet ((ass (x) (+ x 1))) (ass 2))
3
In the above examples, if the calls to 'ass got expanded as macros, then what would be returned is some kind of list, rather than an integer. It's only in Arc that we get:
arc> (mac ass (x) `(list ,x 1))
#(tagged mac #<procedure: ass>)
arc> (let ass (fn (x) (+ x 1)) (ass 2))
(2 1)
By the way, in order to format the above code (add 2 spaces before each line), I used the following handy little 'clipboard function after copying the interactions with the REPLs. I think pbpaste works on all Unix-like systems: