Arc Forumnew | comments | leaders | submitlogin
File I/O Challenge (stackoverflow.com)
3 points by evanrmurphy 2614 days ago | 17 comments


2 points by evanrmurphy 2614 days ago | link

It's not really a challenge, but I submitted this for Arc and tried to make it as concise as possible:

  (w/stdout (outfile "fileio.txt")
    prn!hello
    prn!world)
  (pr:cadr:readfile "fileio.txt")

-----

3 points by rocketnia 2613 days ago | link

The "append" requirement is pretty specific. The file has to be opened for appending, if that's possible. The Python 2 and Groovy examples have complaints along these lines in the comments.

Furthermore, even though I think it was vague in the original description of the problem, the complaints indicate that the "world" line should be read into a variable.

So the first thing I tried was this, riffing off of evanrmurphy's version:

  (w/stdout (outfile "fileio.txt") prn!hello)
  (w/stdout (outfile "fileio.txt" 'append) prn!world)
  (pr:= line (string:cadr:readfile "fileio.txt"))
No luck. The file contains "helloworld" on one line. It doesn't even have a trailing newline. This is thanks to a lack of flushing, which is fixed on Anarki.

Speaking of bugs, do we even close these file handles?

And what if someone's mysteriously edited the file in between our commands, and we end up reading in a million-line file or barfing on a paren mismatch?

Also, most of the responses seem to use "putStrLn", "println", etc. for displaying the line that was read, even though that sacrifices two characters' worth of brevity. :-p

Let's try that again.

  (w/outfile f "fileio.txt" (disp "hello\n" f))
  (w/appendfile f "fileio.txt" (disp "world\n" f))
  (w/infile f "fileio.txt" (repeat 2 (= line readline.f)))
  prn.line
Whoops, I have a stray #\return at the end, 'cause I'm on Windows. To account for that, I can use Anarki and thereby take advantage of aw's 'readline fix and the flushing fix I mentioned earlier. If I use Anarki and take garply's lib/util.arc suggestion, this is what I get:

  (load "lib/util.arc")
  
  ; to be consistent
  (mac w/stdappendfile (name . body)
    (w/uniq gf
      `(w/appendfile ,gf ,name (w/stdout ,gf ,@body))))
  
  (w/stdoutfile "fileio.txt" prn!hello)
  (w/stdappendfile "fileio.txt" prn!world)
  (w/stdinfile "fileio.txt" (repeat 2 (= line (readline))))
  prn.line
That's what I'd settle for in this I/O demonstration, but if it were my own code I'd end up using Lathe, just so I could continue to support official Arc on Windows:

  (= lathe-dir* "my/path/to/lathe/arc/")
  (load:+ lathe-dir* "loadfirst.arc")
  (use-fromwds-as ut (+ lathe-dir* "utils.arc"))
  
  (w/outfile f "fileio.txt" (disp "hello\n" f))
  (w/appendfile f "fileio.txt" (disp "world\n" f))
  (w/infile f "fileio.txt" (repeat 2 (= line ut.readwine.f)))
  prn.line
Unfortunately, this code will break on Jarc 17 and Rainbow, and it's really their fault. :-p Mainly, both of them seem to overwrite the file rather than appending to it, and no workaround for that is coming to mind--well, except for ignoring that part of the problem statement. I'll start a new bug report thread.

For what it's worth, here's how I'd write it in Groovy:

  def file = "fileio.txt" as File
  file.withWriter { it.writeLine "hello" }
  file.withWriterAppend { it.writeLine "world" }
  def line = file.withReader { it.readLine(); it.readLine() }
  println line
My favorite posted answer is this PowerShell one. The comments lead me to believe it's cheating somehow, but the brevity is really impressive. This is pretty much what I'd expect a file I/O DSL to look like.

  sc fileio.txt 'hello'
  ac fileio.txt 'world'
  $line = (gc fileio.txt)[1]
  $line

-----

2 points by waterhouse 2613 days ago | link

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 2613 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 2613 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 2613 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 2613 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 2612 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.

-----

1 point by garply 2614 days ago | link

If we can count w/stdoutfile from lib/util.arc as part of the Arc language (and I'd argue that we should), we could drop another token:

  (w/stdoutfile "fileio.txt"
      prn!hello
      prn!world)
    (pr:cadr:readfile "fileio.txt")
10 tokens is pretty good - Skimming the list, I think Arc pretty much mops the floor in terms of concision.

-----

4 points by fallintothis 2613 days ago | link

Hm. Anarki calls them w/stdoutfile and w/stdinfile, but wouldn't tofile and fromfile be more consistent with tostring and fromstring (and shorter, besides)? Then there's the whole appending business. Can't really give an optional parameter to tofile like outfile has, and toappendfile runs together horribly. What about just appendfile?

Then, using readline instead of readfile so that it (1) doesn't try to parse things as s-exprs and (2) doesn't read the entire file for no good reason, I'd envision it thus:

  (tofile "fileio.txt" (prn "hello"))
  (appendfile "fileio.txt" (prn "world"))
  (fromfile "fileio.txt" (readline) (pr (readline)))

-----

1 point by waterhouse 2613 days ago | link

Excellent. Have added to my personal Arc library (which is a big fat file named "a", to which I keep appending things):

  (mac tofile (f . body)
    (w/uniq gf
      `(w/outfile ,gf ,f
         (w/stdout ,gf
           ,@body))))
  (mac fromfile (f . body)
    (w/uniq gf
      `(w/infile ,gf ,f
         (w/stdin ,gf
           ,@body))))
I'm not sure about the appendfile--given what infile and outfile do, it sounds like a procedure that creates an output-port that appends to a file. Maybe to-appendfile, appendtofile, appendingfile, tofile/append... Alternatively, we could make keyword arguments happen in Arc, and then you would just throw ":append t" or something inside the call to tofile. That would also allow for further extension with, e.g., an :if-exists argument.

-----

1 point by rocketnia 2613 days ago | link

How about 'tolog? Are files opened for appending for other reasons, in practice? This would also keep with the to-means-output, from-means-input pattern.

-----

2 points by fallintothis 2613 days ago | link

I'd try to err on the side of generality. And I'm not quite as concerned about to:output / from:input, if the names are still "clear enough".

As to waterhouse's suggestions, I had considered those names. I suppose if you read appendfile as a noun instead of a verb-and-noun, it's confusing (though infile and outfile don't really have the same problem, so it's not the train of thought my brain follows). It's hard modifying a name like tofile with a long word like append. We already have two words in tofile, so adding a third without hyphenation is stretching it, and adding hyphens breaks the flow with the other names (fromfile, tostring, etc.). We could go for something shorter, like addtofile, which delineates itself well without hyphens because each word is one syllable. If we can't avoid hyphens, using / instead (e.g., tofile/a or tofile/append) flows better, but isn't that great.

Another name that occurred to me -- and is probably my favorite so far -- is ontofile, which is still simple enough to not need hyphens, communicates intent (appending something onto a file), and worms the word to in there, painting it with the to:output / from:input correlation. Thoughts?

-----

2 points by evanrmurphy 2613 days ago | link

Another name that occurred to me -- and is probably my favorite so far -- is ontofile, which is still simple enough to not need hyphens, communicates intent (appending something onto a file), and worms the word to in there, painting it with the to:output / from:input correlation. Thoughts?

+1! ontofile is a great name, in my opinion, for all the reasons you listed.

I searched for a good portmanteau in the vein of mappend, but I don't think there is one. fappend? Sounds like frappuchino. filepend is decent, but I think I prefer ontofile.

-----

2 points by fallintothis 2613 days ago | link

fappend? Sounds like a frappuchino.

Hahaha! Or worse: http://www.urbandictionary.com/define.php?term=fap :P

filepend is decent, but I think I prefer ontofile.

Agreed on both counts. But that's a clever one; I hadn't thought to try out portmanteaus yet.

-----

1 point by evanrmurphy 2613 days ago | link

Oh wow, didn't know that one. Who knew you could get street smarter hanging out on Arc Forum??

-----

1 point by garply 2613 days ago | link

Regarding logging, I use a log function such that (log "my log message here") appends to a globally identified file log*.

-----