I don't remember if I've brought this up before, but in Jarc, (catch:list:point ignored throw.nil) returns (nil) instead of nil. This means we can't really use escape continuations in a library function if the (catch ...) form surrounds a call to a parameter, 'cause doing that changes the effective behavior of the function when the library user is also using escape continuations.
That said, please don't fix this unless you also change 'on-err so that it doesn't catch escape continuations. I'm currently using this bug to work around that one, by re-throwing caught escape continuations with a (catch.throw value.the-error) idiom. :-p
If it's two or more actually corecursive functions you need, you can use a manual letrec pattern. (Is CL's 'labels basically the same as Scheme's 'letrec?)
(let (odd even) nil ; destructuring, so both start as nil
(= odd [case _ 0 nil (even:- _ 1)])
(= even [case _ 0 t (odd:- _ 1)])
odd.12)
In fact, if you're using Anarki (http://github.com/nex3/arc), this kind of letrec is already defined as 'withr:
(withr (odd [case _ 0 nil (even:- _ 1)]
even [case _ 0 t (odd:- _ 1)])
odd.12)
(use-rels-as ut (+ lathe-dir* "utils.arc"))
; The bindings all have to be everyday variable names, but this
; detects them automatically. You can use with-style parentheses too,
; in case you need to be extra sure.
(ut:letrec odd [case _ 0 nil (even:- _ 1)]
even [case _ 0 t (odd:- _ 1)]
odd.12)
I'm sure this doesn't surprise you, but here's a quick version of 'defclass that uses a syntax similar to your first example and an implementation similar to your second example:
Nice, and you even changed it so x!deposit returns a function again! This does of course add some overhead since a closure is constructed every time you call a method, but still.
One thing I'm not quite happy with is that one has to write o!money. Would it somehow be possible to hide the o? Would it be possible to use !money or .money, or does the parser not allow that? And how to pass the hash table from the afn to the methods without polluting their namespaces? It could be done using a gensym, but then it is not possible to add methods to the method table outside defclass.
3) Make a batch file that'll run an Arc REPL for me when I double-click it. Here's my batch file, with a boatload of comments to help you figure out what I'm doing: http://gist.github.com/576688 There's probably some room for improvement, but it's what I'm happily using at the moment. (Note that it crashes a bit if you close the terminal window while Racket 5.0.1 is running, instead of exiting via a Racket (exit) call or an Arc (quit) call. I think this is a Racket issue, since previous versions don't crash that way.)
4) Make some necessary changes to Arc so that it stops giving errors on Windows. There are two main issues here. For Arc to start at all, you'll need to fix 'setuid (http://arclanguage.org/item?id=10625). Then, if you also want to run web applications, you'll probably need to fix the system call to mkdir (http://arclanguage.org/item?id=4652), and I wouldn't be surprised if one of the other system calls breaks too.
If you've already installed Cygwin, the system call issues may not exist, but I'm not totally sure of that. I know I only encountered the mkdir issue on a Cygwin-free Windows computer after having used Arc on a Cygwin-ful Windows computer for quite a while... but I'm not sure I've ever actually tried to run Arc web applications on the Cygwin-ful computer.
Tip: At least for me (on a Mac), running Arc with "[racket|mzscheme] -f as.scm" and then hitting ctrl-C at some point will kill the whole process, instead of dropping to a [Racket|Scheme] prompt from which I can use (tl) to return to the arc> prompt. Replacing the command with "[racket|mzscheme] -i -f as.scm", -i for interactive, fixes this. Much better.
I used to have 'cd ~/Dropbox/arc3.1;' before the 'rlwrap ...' part; the aload function and all the Arc files that load other Arc files (libs.arc) assume that you're in the Arc directory. I worked around this by wrapping all the calls to 'aload in as.scm like this:
- Jarc doesn't support coercion from 'sym to 'int.
- Jarc's 'readc returns a spurious -1 character rather than the given end-of-file value. This has further consequences, like causing (readline:instring "") and (prf "anything") to go into infinite loops.
- Rainbow doesn't support 'macex1.
- Rainbow doesn't accept the syntax [].
- Rainbow chokes on (after (err "ignored") errsafe!ignored). In general, it's frustrating to do anything nontrivial with 'after (or 'protect), because an 'on-err anywhere in the "finally" call tree will mess things up.
- Rainbow raises an error when 'rep is called with an untagged argument.
- Both Rainbow and Jarc 17 handle (annotate 'a (annotate 'b 'c)) so that it's distinct from (annotate 'a 'c). Actually, the official Arc behavior is what caught me by surprise here, and I've ended up using the workaround (annotate 'a (list:annotate 'b 'c)) with my own tagged types, but maybe this tag-collapsing behavior is something you'd like to emulate anyway.
What is the issue with [] in rainbow? ... Do you have an example?
I think you'll find it's a very simple example. :-p
arc> []
rainbow.parser.ParseException: Encountered "]" at line 1, column 2.
As for 'macex1, there's definitely one inconsistency, which is that it doesn't work unless car.expr works. I also briefly worried about ssyntax, but I'm not sure it's a problem; official Arc lets (assign a.b nil) cause (bound 'a.b) to be true, but I don't blame Rainbow for not emulating that.
- Jarc doesn't support coercion from 'sym to 'int.
I don't remember what I meant by this. Official Arc doesn't support this either. XD
I must have done some test that behaved one way on official Arc 3.1, Anarki, and Rainbow and a different way on Jarc (17), but I guess I discarded too many necessary details here.
- Both Rainbow and Jarc 17 handle (annotate 'a (annotate 'b 'c)) so that it's distinct from (annotate 'a 'c).
Whoops, official Arc does that too. I should have given the examples (annotate 'a (annotate 'a 'b)) and (annotate 'sym 'b); official Arc's 'annotate doesn't wrap the value if it's already of the given type.
In case you didn't know, the point of 'atpos is to implement atstrings, an optional string interpolation feature that lets "@qty @(plural qty \"taco\") @@ $@price" compile so that it evaluates to things like "2 tacos @ $11". This option isn't enabled by default, so any Arc program that uses atstrings, such as news.arc, has to (declare 'atstrings t) first.
IMO, an @ at the end of an atstring is a syntax error, since it isn't escaping anything. It might be convenient to give "...@" a meaning like (sym "..."), (err "..."), or even just "...@@", but that's adding functionality rather than fixing bugs.
For what it's worth, I practically never add numbers. I actually use subtraction much more than addition! So I don't mind whatever these operations are named--and I mind the speed even less, actually--but I can testify that this change would sorta hinder my own code at this point. :-p
You've almost certainly encountered this comment above the definition of 'join, but I'll paste it here just in case it elucidates anything:
I make heavy use of + for concatenation throughout my code. I prefer it for a few reasons:
1. I find myself concatenating lists frequently and I prefer that frequently used functions be short. join has 4 chars to +'s 1
2. I also find ++ convenient for modifying a list variable. What would you use for the equivalent for join? In Racket's style, it would be join!, but I don't see a good analogue in arc for your proposal.
3. I'm constantly doing string concatenation. + is good for that because it's a short function name and also because I expect it to work because it works like that in many popular high-level languages (python, ruby, javascript). I also don't like to use arc's "@" for string-escaping because I find thinking about whether or not I have to escape the "@" character is distracting.
With regard to (2), to destructively append the list '(1 2 3) to xs, you can:
(zap join xs '(1 2 3))
"zap join" is several characters longer than ++, but zap has general utility.
I use the string function to concatenate strings. It seems to work identically to +, as long as the first argument is a string. I do give heterogeneous arguments to string usually, and I like seeing that they will clearly be coerced into a string.
I have a couple of ideas.
1. It would be little problem for me if Arc provided + as is and provided a "plus" function that worked only on numbers and allowed me to simply go (= + plus) and proceed normally. Unfortunately, that would break all the functions in arc.arc and so forth that use + to concatenate lists (which includes, among other things, the setforms function, which is used in expand=). It would be really nice if one could "freeze" the variable references in those functions, so that changing the global value of + wouldn't change what "+" in the bodies of those functions referred to.
2. If you use concatenation so much, perhaps we could allocate an ssyntax character for concatenation. & is currently used for andf (experiment with ssexpand to see). We could boot out that usage, or perhaps have "&&" become andf. Good/bad idea? (Precedent: my memory tells me the TI-89 uses & for string concatenation.)
Regarding your second suggestion, we could also use . instead of &, as that's what Perl and PHP do - feels a little more natural to me. But . might cause me a little mental friction in differentiating between the different uses of . in (. "a" "b") and (do (= a '(1 2)) a.0).
To be honest, I'm still not crazy about the idea simply because I don't need the speed boost and + doesn't seem to cause me to use extra mental cycles when reading my code. I'd be open to it though if the community really wanted it that way.
We could vote and also ask PG what he thinks and then make a decision.
String concatenation is particularly convenient after the + change for arc3.tar: http://arclanguage.org/item?id=9937. That's probably what I use + for most of the time.
I find thinking about whether or not I have to escape the "@" character is distracting
I find this is easier with proper syntax highlighting. My arc.vim ftplugin can detect if you have (declare 'atstrings t) and, if so, highlights the escaped parts of strings. That way, you know if @ is escaped just by glancing. But I don't mean to shamelessly plug, haha. I don't use atstrings either, but my reason is far lazier: in the middle of writing code, it's less effort to just use + than it is to declare then go back and start using @s.
What other goodies does your arc.vim plugin have? Is your editor at all integrated with the arc repl? Lack of a repl that I could easily send arc code to was the reason I switched to emacs after years of using vim. These days, using emacs with viper (vim emulation mode), I don't miss vim at all.
Am i wrong if I think that, in the end, the main difference between 'def'+'eval' and 'mac' is that 'mac' allows its code to be evaluated and expanded once, whereas 'def' needs to expand its code every time it is called ?
It's 'eval that expands its code once every time it's called. If you don't use 'eval at all, then all your arc code will be expanded exactly once, at the time you enter it at the REPL or load it from a file (since those are the points where 'eval is called internally).
When you enter the expression (def foo (x) (obj key x)) is at the REPL, it's evaluated as a two-step process: First it's compiled (which expands the (def ...) and (obj ...) macro forms), and then the compiled code is run. When it's run, the body of the function isn't run yet; instead, it's packed up into a procedure and stored as foo. When you call foo later, no expansion takes place, only running of already compiled code.
A better (but not perfect) way to use 'eval equivalently to 'mac is something like this:
(def mywhen (test . body)
`(if ,test (do ,@ body))
(eval `(time:for i 1 1000 ,(mywhen '(is i 666) '(prn "Oh no!"))))
If we wanted to make 'do a non-macro, we could do that too:
(def mydo body
`((fn () ,@body)))
(def mywhen (test . body)
`(if ,test ,(apply mydo body)))
(eval `(time:for i 1 1000 ,(mywhen '(is i 666) '(prn "Oh no!"))))
Finally, if we wanted to avoid the use of macros altogether, the example would turn into something like this:
; The procedure corresponding to an Arc macro can already be obtained
; using 'rep.
(assign mywhen rep.when)
(assign mytime rep.time)
(assign myfor rep.for)
(eval:mytime:myfor 'i '1 '1000 (mywhen '(is i 666) '(prn "Oh no!")))
I could make a few guesses as to why this kind of programming isn't popular in lisps, but my personal reason is that I tend to consider all operators to provide their own syntax, with procedure call syntax just being the default choice. For me, it's inconsistent to have the '(prn ...) syntax be quoted when the (mywhen ...) syntax isn't.
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.
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?
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.
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:
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:
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.