Arc Forumnew | comments | leaders | submit | waterhouse's commentslogin
2 points by waterhouse 5576 days ago | link | parent | on: File I/O Challenge

I'm afraid I'll have to nitpick.

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 point by evanrmurphy 5576 days ago | link

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.

---

Update: I submitted your version to http://stackoverflow.com/questions/3538156/file-i-o-in-every... with an attribution.

-----

1 point by waterhouse 5576 days ago | link

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))

-----

1 point by evanrmurphy 5576 days ago | link

How about this variation of your latest:

  (w/infile f "fileio.txt"
    (repeat 2 (= l readline.f))
    (prn l))
While a bit longer, it eliminates the duplication of readline.f and addresses the concern about not reading "world" into a variable.

-----

1 point by waterhouse 5576 days ago | link

The original spec says this...

  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.

-----

3 points by fallintothis 5575 days ago | link

Otherwise, I think I'm satisfied with this code.

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

  (tofile   "fileio.txt" (prn "hello"))
  (ontofile "fileio.txt" (prn "world"))
  (fromfile "fileio.txt" (prn (readlines 2)))
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.

  (def all-lines ((o str (stdin)))
    (drain (readline str)))

  (def filelines (name)
    (fromfile name (all-lines)))
Not sure if the latter should be hyphenated, though.

-----


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.:

  (no:= foo (string:n-of 100000 "@_"))
  ;instead of
  (do1 nil (= foo (string:n-of 100000 "@_")))
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.)

-----

4 points by fallintothis 5578 days ago | link

On another note, it seems "~" does the same thing as "no:", which I think is not much of an improvement

Relevant: http://arclanguage.org/item?id=3564

-----


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.

  ;Assuming ($ x) = "evaluate x in Scheme"
  (mac mem-use body
    (w/uniq x
      `(let ,x ($.current-memory-use)
         ,@body
         (- ($.current-memory-use) ,x))))

  ;In action:
  arc> (mem-use (+ 1 2))
  32
  arc> (mem-use (= x range.10))
  2528
  arc> (mem-use (= x (range 1 10)))
  1680
  arc> x
  (1 2 3 4 5 6 7 8 9 10)
  arc> (mem-use (* 1 2))
  0
  arc> (mem-use ($.collect-garbage))
  -7115280
  arc> (repeat 5 (prn (mem-use:$:collect-garbage)))
  -88828
  -284
  -280
  -280
  -280
  nil

-----

1 point by evanrmurphy 5578 days ago | link

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.

-----

3 points by waterhouse 5582 days ago | link | parent | on: Named loop or continue keyword?

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.

-----

3 points by waterhouse 5584 days ago | link | parent | on: Try Arc

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:

  (def eval-w/stdout (expr)
    (tostring:let result nil
      (let output (tostring (= result (eval expr)))
        (unless (empty output)
          (prn "output: " output))
        (write result))))
In action: (for some reason, when I copied this, it had a bunch of extra spaces)

  arc> (eval-w/stdout '(time (reduce + (n-of 1000 rand.100))))
  output: time: 339 msec.
  
  49840
[0]http://arclanguage.org/item?id=11838

-----

2 points by evanrmurphy 5583 days ago | link

Try Arc saw two updates today:

1. stdout now displays properly.

  arc> (pr "hello")
  hello"hello"
@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):

   (def eval-w/stdout (expr)
  -  (tostring:let result nil
  -    (let output (tostring (= result (eval expr)))
  +  (tostring:withs (result nil
  +                   output (tostring (= result (eval expr))))
         (unless (empty output)
           (prn "output: " output))
  -      (write result))))
  +      (write result)))
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.

-----

2 points by evanrmurphy 5582 days ago | link

I added support for multi-line entry:

  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.

-----

2 points by evanrmurphy 5584 days ago | link

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.

-----

2 points by waterhouse 5592 days ago | link | parent | on: rif

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.)

-----

1 point by evanrmurphy 5592 days ago | link

Somehow it had escaped me that the r stood for "recursive". I thought maybe it stood for "reference", in which case rif would be a sensible name.

I think you're right this definition could just override iflet. It would be an obscure case where you wouldn't want var binding to the other exprs.

-----

6 points by waterhouse 5598 days ago | link | parent | on: Why make-br-fn?

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.

See also: http://arclanguage.org/item?id=1227, http://arclanguage.org/item?id=8947, http://arclanguage.org/item?id=8617

-----

1 point by rntz 5596 days ago | link

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.

-----

1 point by evanrmurphy 5598 days ago | link

OK, that makes sense. Thanks for the links.

-----


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.

-----

1 point by bogomipz 5606 days ago | link

Wow, it seems to work fine with Racket 5.0, and I don't notice any issues with the prompt.

This should be mentioned on http://www.arclanguage.org/install

Thanks for the hint, evanrmurphy!

-----

1 point by waterhouse 5606 days ago | link

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.

-----

1 point by prestonbriggs 5606 days ago | link

Not for me. Nor does it work with mzscheme. I get the complaint

ac.scm:1023:0: ffi-obj: couldn't get "setuid" from #f (The specified procedure could not be found.; errno=127)

Preston

-----

1 point by waterhouse 5606 days ago | link

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:

  (define setuid (get-ffi-obj 'setuid #f (_fun _int -> _int)))
and replace it with

  (define (setuid x) x)
I have done this on at least two Windows computers and Arc ran fine afterwards.

[1]Source: http://arclanguage.org/item?id=10625

-----

1 point by prestonbriggs 5605 days ago | link

Got it, thanks.

-----

2 points by ylando 5605 days ago | link

Why arc do not have a normal web page; See:

  http://www.ruby-lang.org/en/
  http://www.python.org/
  http://www.perl.org/
  http://www.erlang.org/
  http://clojure.org/

-----

2 points by akkartik 5605 days ago | link

Because it's unfinished (and may remain so). See http://arclanguage.org and more recently http://news.ycombinator.com/item?id=1525323. No point sucking people in with a normal-looking webpage if the language isn't really ready for production use.

-----

1 point by evanrmurphy 5603 days ago | link

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?

-----

2 points by akkartik 5603 days ago | link

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.

Not all kinds of production are made the same.

-----


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:

  arc> (= i\'m-a-symbol 2)
  2
  arc> (+ |i'm-a-symbol| 3)
  5
  arc> (list 'bob\ the\ hun '\( '\ )
  (|bob the hun| |(| | |)
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))"

-----

2 points by evanrmurphy 5687 days ago | link

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):

  arc> (= |()| "escaped empty set")
  "escaped empty set"
Like typical escaped characters, it isn't eq to nil and () unless you assign it that way:

  arc> (is () |()|)
  nil
  arc> (= |()| nil)
  nil
  arc> (is nil () |()| \(\))
  t
[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:

  (def clipboard () (tostring:system "pbpaste"))
  
  arc> (let u (instring:clipboard) (whilet a readline.u (prn "  " a)))
    > (define (meh ass)
        (ass 1))
    > (meh (lambda (x) (+ x 1)))
    2
  nil

-----

More