Note that assert-same requires the expected value before the test-value. This is needed for pretty, pretty messages when the assert fails:
Maybe I'm just tired, but I first took this to mean that it was impossible for assert-same to be defined so that its arguments were (actual expected (o fail-message)). Now I realize that you just mean the positions of your expected/actual values are important.
One design thought: the name assert isn't taken by Arc. You could simplify the interface while generalizing it by just separating assert from the things it's asserting.
So, the basic assert would essentially be assert-t, and the other three could follow. Something like
(assert-t actual (o fail-message))
; becomes
(assert actual (o fail-message))
; or, without the custom message
(assert actual)
(assert-nil actual (o fail-message))
; becomes
(assert (no actual) (o fail-message))
; or, without the custom message
(assert:no actual)
(assert-error actual (o expected-error))
; becomes
(assert (error actual (o expected-error)) (o fail-message))
; or, without the custom message
(assert:error actual (o expected-error))
(assert-same expected actual (o fail-message))
; becomes
(assert (same expected actual) (o fail-message))
; or, without the custom message
(assert:same expected actual)
Then, you define error and same to "do the right things". same would probably need to throw an error when the arguments were mismatched, so that you could generate the nice message---assuming that assert would catch errors and report them as failures. error would be easy to define. Maybe something like
(mac assert (condition (o fail-message))
`(on-err (fn (c) ...fail...)
(fn () (unless ,condition ...fail...))))
(mac same (expected actual)
(w/uniq (e a)
`(with (,e ,expected ,a ,actual)
(or (iso-that-works-on-hash-tables ,e ,a)
(err "" ',actual 'returned ,a 'wanted ,e))))) ; modulo prettiness
(mac error (expr (o expected-error))
`(on-err (fn (c) ...pass, modulo expected-error...)
(fn () ,expr ...fail if we get here without an error...)))
Thanks for the feedback, both here and on the bug in the bitbucket tracker.
I'm not sure of my feelings on changing all the asserts to be just one. On one hand, it is simpler to have just the one command. On the other, it seems like a much more complicated interface, as you would never guess how to use it from, for example, the method signature. assert-t, assert-same et. al. seem like a lower conceptual load.
It reminds me of the arguments over CL's loop macro -- who knows all of it? I can never remember how to do much of what I want. Of course, this is much simpler than loop. Maybe I'll implement it and see how it feels.
The error you're getting from fancy-combine2 is because you have a double-unquote. You neglected to remove the unquotes from sym1 and sym2. The correct form would be
It might help to have a user-land defined quasiquote macro, to see how it works. I implemented such a macro here: https://bitbucket.org/fallintothis/qq
Using qq.arc, we can see that my solution is to use this expression:
Hence, you get an error about unquote being undefined, because Arc doesn't actually define quasiquote or unquote as macros. They're just special identifiers that are basically parsed away before evaluation (assuming quasiquotes/unquotes are balanced). However, loading qq.arc defines an unquote macro, so instead of the error you had, you'll get this:
arc> (fancy-combine2 a b)
Error: "unquote not allowed outside of a quasiquote: a"
which makes it a bit more obvious what the error is.
For some reason, I had in my head that, inside quasiquote in a macro, you need to unquote every variable you want to evaluate before returning the code to be executed. Note that all my examples have ,sym1 and ,sym2 (except fancy-combine4, but that's deliberately executing the code before the returned generated code).
I guess the moral of the story here is that ,(...) evaluates the entire sexp, and puts the value there. I'll have to do more reading about macros.
I guess the moral of the story here is that ,(...) evaluates the entire sexp, and puts the value there.
Yup! If your quasiquoted expression only contains unquotes, the expansion is simple. Basically,
`(sexp1 sexp2 ... ,sexpn ...)
will expand into
(list 'sexp1 'sexp2 ... sexpn ...)
This also applies recursively, so that
`(sexp1 (subexpr1 ,subexpr2))
expands into
(list 'sexp1 (list 'subexpr1 subexpr2))
With unquote-splicing, you can think of quasiquote expanding into cons, so
`(sexp1 sexp2 ,@sexp3)
will expand into
(cons (list 'sexp1 'sexp2) sexp3)
If you're interested, I encourage you to look over my implementation of quasiquote in Arc. It's actually pretty simple (175 lines with comments), and might help clarify how quasiquotation works.
I'll have to do more reading about macros.
I think you seem to have the mental model for macros down. And I've always said that macros aren't that magical, because they fall out as a natural consequence of the language. Hell, here's a simple Arc "interpreter" that shows how macros are handled differently from functions:
(def interpret (expr)
(if (acons expr)
(let op (interpret (car expr))
(if (isa op 'mac) (interpret (apply (rep op) (cdr expr)))
(isa op 'fn) (apply op (map interpret (cdr expr)))
(eval expr))) ; punt on special forms (fn, if, etc.)
(maybe-lookup-name expr)))
(def maybe-lookup-name (expr)
(if (and (isa expr 'sym) (bound expr))
(eval expr)
expr))
Here, interpret is really just another name for eval. Macros are simply functions whose inputs are unevaluated code. They construct some other bit of unevaluated code, and that result gets evaluated.
Quasiquote is then just a nice way to construct lists, whether it's unevaluated code for a macro to return or whatever. Its syntax takes some getting used to, but it comes with practice. Happy hacking!
Editing news.arc won't "reload" your changes. If you write the code to the file, it won't actually execute that code. Does that make sense?
You want to go back to the arc> prompt (where you started (nsv)) and do something to get that code to run. You could do this in a few ways. You could do:
arc> (load "news.arc")
which will reload the changes you saved to the news.arc file. It might look crazy, because it prints out a bunch of messages that look like
*** redefining logins-page
It'll do that because it's reloading the entirety (!) of news.arc, which includes a bunch of other code. Thus, there are warnings about "redefining" certain code. But as long as you didn't actually change the code in question, it should be fine.
At the end of the day, this is probably the best way to go, despite all the intimidating warning messages. Because if you edit the news.arc file, then the changes you make will be saved in case you ever need to restart the forum in the future.
But, for the sake of completeness, I'll explain how you can also just edit the variables by hand at the prompt. So, instead of changing news.arc to say
(= this-site* "Match Fixing Scandals"
site-url* "http://news.yourdomain.com/"
parent-url* "http://www.yourdomain.com"
favicon-url* ""
site-desc* "What this site is about." ; for rss feed
site-color* (color 180 180 180)
border-color* (color 180 180 180)
prefer-url* t)
and reloading news.arc, you could actually just put the entire thing above (called an expression) into the arc> prompt:
arc> (= this-site* "Match Fixing Scandals"
site-url* "http://news.yourdomain.com/"
parent-url* "http://www.yourdomain.com"
favicon-url* ""
site-desc* "What this site is about." ; for rss feed
site-color* (color 180 180 180)
border-color* (color 180 180 180)
prefer-url* t)
This will execute the code, thus modifying your settings.
You could also use simpler expressions---or, really, any Arc code you want. If you weren't a coder before, you are one now! :)
The expression above of the form (= a b c d ...) can be used to change the values of any number of variables (setting a to b, c to d, etc.), which will control how the site displays. So, you could do something like
One of these settings you'll want to change is buried deep inside of news.arc (about 400 lines down), so it's hard to spot. But, you can change these variables, too:
I tried to put the entire expression into the arc> prompt and also tried to use simpler expressions, but I still can't get "My Forum" to change.
I've also reloaded news.arc several times and I'm not sure if it means anything, but when I go back to the arc> prompt and enter
arc> (load "news.arc")
I get
nil
and only after loading news.arc a second time I get the
"* redefining" messages
I remember that to run news.arc the first time I also used (nsv) so I tried that as well and for what is worth I got the following error:
rm: cannot remove ‘arc/news/story/*.tmp’: No such file or directory
load items:
user break
context...:
/home/ubuntu/arc/ac.scm:1031:20
gs1259
user break
context...:
/home/ubuntu/arc/ac.scm:1031:20
gs1259
Error: "tcp-listen: listen failed\n port number: 8080\n system error: Address already in use; errno=98"
arc>
Hmmm it's been a really long time since I looked at the code, but I would try two things.
1. Enter this-site* at the prompt to confirm the change took place.
2. Abort the session, I.e such that the arc prompt is killed. Then goto the webpage and make sure its no longer running news (just to confirm the abort occurred and/or is not somehow running a past instance), then reload arc again.
Yeah these are good suggestions. The "address already in use" error suggests that you might have multiple copies of arc running, so that you're making changes to the wrong copy. Use the commands 'ps' and 'kill' to look for the other copies and stop them.
I used the command
ps aux | grep 'news.arc'
and it returned only one copy running, but I suspect that's not right.
And your comment about multiple copies also got me thinking that from the main Arc directory to go into the Arc prompt maybe I shouldn't have been using
$ mzscheme -f as.scm
Was I supposed to use that just once to run news.arc for the first time?
Assuming you're using 'GNU screen' you also have the option to re-attach to an existing session using the 'screen -r' command. Using 'screen -ls' will list out all the sessions should you not be sure, or have more than one.
That aside, most developers will do development changes on a local machine where they need not attach/reattach and this way you wouldn't need to troubleshoot your code and the host environment at the same time.
Then you create a stable deployment process. eg. Backup the old code, drop in the new code, re-attach to the existing session and either load your changes or restart the program if you're ok with some nominal downtime.
No problemo and yeah, in retrospect, I could have chosen better terminology and been much more descriptive, but that's one of the things you will enjoy about the arc forum... You just keep asking and, generally speaking, people are very helpful. After all, I learned how to program by just doing what you are, running the news app, reading the arc code and relying on this forum for help along the way.
First, you have to understand what the quasiquote and unquote are doing. It doesn't seem at first glance like you're clear on that, so a mini-lesson:
arc> 'the-basic-purpose-of-quote-is-to-prevent-a-symbol-from-being-evaluated
the-basic-purpose-of-quote-is-to-prevent-a-symbol-from-being-evaluated
arc> '(quote can be used on any expression --- including a list)
(quote can be used on any expression --- including a list)
arc> (list 'you 'can 'think 'of ''(a b c) 'as 'shorthand 'for '(list 'a 'b 'c))
(you can think of (quote (a b c)) as shorthand for (list (quote a) (quote b) (quote c)))
arc> `quasiquote-is-pretty-much-just-like-quote
quasiquote-is-pretty-much-just-like-quote
arc> (list 'so '`(a b c) 'is 'like '(list 'a 'b 'c))
(so (quasiquote (a b c)) is like (list (quote a) (quote b) (quote c)))
arc> `(but suppose you want to write (list 'a 'b c) --- where the last element has no quote)
(but suppose you want to write (list (quote a) (quote b) c) --- where the last element has no quote)
arc> `(then quasiquote gives you a shorthand by using "unquote")
(then quasiquote gives you a shorthand by using "unquote")
arc> `(so `(a b ,c) is like (list 'a 'b c) and `(a ,b c) is like (list 'a b 'c) etc)
(so (quasiquote (a b (unquote c))) is like (list (quote a) (quote b) c) and (quasiquote (a (unquote b) c)) is like (list (quote a) b (quote c)) etc)
arc> `(thus this list will have 4 instead of (+ 2 2) right here: ,(+ 2 2))
(thus this list will have 4 instead of (+ 2 2) right here: 4)
arc> `(because `(a b ,(+ 2 2)) is like (list 'a 'b (+ 2 2)))
(because (quasiquote (a b (unquote (+ 2 2)))) is like (list (quote a) (quote b) (+ 2 2)))
arc> (list 'see? 'a 'b (+ 2 2))
(see? a b 4)
arc> 'voila
voila
With that understood, each isn't doing what you think it is.
arc> (each x '(1 2 3)
(prn "\"each\" is used for side-effects on the variable x = " x)
(prn "but ultimately, it will return nil")
(prn "we can call pure functions on x")
(+ x 1)
(prn "but we won't see anything they return")
x
(* x 12)
(when (is x 3)
(prn "witness: the next line is the return value of \"each\"!")))
"each" is used for side-effects on the variable x = 1
but ultimately, it will return nil
we can call pure functions on x
but we won't see anything they return
"each" is used for side-effects on the variable x = 2
but ultimately, it will return nil
we can call pure functions on x
but we won't see anything they return
"each" is used for side-effects on the variable x = 3
but ultimately, it will return nil
we can call pure functions on x
but we won't see anything they return
witness: the next line is the return value of "each"!
nil
This is because each is a macro that (on a list) expands
(each x xs
(do-something x)
(do-something-else x))
You can see that when we get to the end of the list xs, the variable elements is passed in as nil. Because (acons nil) is nil, we don't return anything from the overall when expression. As such, you can be sure that the return value of each on a list is always going to be nil, because that's how it ends its recursive journey.
Thus,
arc> `(if I "unquote" (each x '(1 2 3) x) into this list I just get ,(each x '(1 2 3) x))
(if I "unquote" (each x (quote (1 2 3)) x) into this list I just get nil)
What you actually want is to accumulate the results of manipulating each element of the list. That's what map is for:
arc> (map (fn (x) (+ x 1)) '(1 2 3))
(2 3 4)
arc> `(if I "unquote" (map [* _ 12] '(1 2 3)) into this list I get ,(map [* _ 12] '(1 2 3)))
(if I "unquote" (map (fn (_) (* _ 12)) (quote (1 2 3))) into this list I get (12 24 36))
So, let's take a look at how
(mac css (sel props . nest)
`(do ,(pr (string sel "{"))
,(gen-css-properties props)
,(map [gen-nested-css _ sel props] nest)))
works out. But let's take it slow and just use macex1 to expand the macro by one step---I don't want to get lost in recursion. Because your macro is making calls to pr at macro-expansion time, I'm going to take the liberty of drawing it out even further, so as the output from the prompt doesn't get too confusing:
arc> (let macro-expansion (macex1 '(css "#foo" (id 5) (".bar" (z-index 2))))
(prn)
(ppr macro-expansion)
(prn))
#foo{
(do "#foo{"
(do (if 5 (pr "id:" 5 ";"))
(pr "}"))
((css "#foo .bar"
(id 5 z-index 2)
nil)))
nil
Now, we get to see the problem of using the unquote versus unquote-splicing (,@). The unquote will just shove its argument into the list. Because we're unquoting another list itself, you can see the result of the call to gen-nested-css gets stuck with an extra layer of parentheses. In the expansion, it's like saying
However, if you use unquote-splicing, the elements of the list will get "inlined" into the overall list that the quasiquote is constructing. E.g.,
arc> `(symbols wrapped in parens because of unquote: ,'(a b c))
(symbols wrapped in parens because of unquote: (a b c))
arc> `(symbols without parens because of unquote-splicing: ,@'(a b c))
(symbols without parens because of unquote-splicing: a b c)
So,
(mac css (sel props . nest)
`(do ,(pr (string sel "{"))
,(gen-css-properties props)
,@(map [gen-nested-css _ sel props] nest)))
gives us
arc> (let macro-expansion (macex1 '(css "#foo" (id 5) (".bar" (z-index 2))))
(prn)
(ppr macro-expansion)
(prn))
#foo{
(do "#foo{"
(do (if 5 (pr "id:" 5 ";"))
(pr "}"))
(css "#foo .bar"
(id 5 z-index 2)
nil))
nil
Now, whether this is the desired expansion at the end of the day is a matter of debugging. Good luck! :)
Recommendation: with these points (I hope) cleared up, look closer at html.arc. If you had modeled your code after it more precisely, you probably wouldn't have had these issues. But hey, that's how we learn.
I catch some resistance sometimes when I suggest learning Arc by reading Arc, but the source code is not only a definitive reference for what functionality Arc provides, it also gives you a wealth of idiomatic code. And I can't think of a better way to answer "how does [some function] work?" than to look up its usually-terse definition.
arc> (= y (= x '(1 3 5)))
(1 3 5)
arc> (insort < 2 x)
(1 2 3 5)
arc> x
(1 2 3 5)
arc> y
(1 2 3 5)
arc> (insort < 0 x)
(0 1 2 3 5)
arc> x
(0 1 2 3 5)
arc> y
(0 1 2 3 5)
arc> (insort < 6 x)
(0 1 2 3 5 6)
arc> x
(0 1 2 3 5 6)
arc> y
(0 1 2 3 5 6)
arc> (= y (= x nil))
nil
arc> (insort < 1 x)
nil
arc> x
nil
arc> y
nil
Here's a version I wrote for my own edification (basically, a more complicated version of yours):
(def destructive-cons (elt seq)
; Note: (push elt seq) won't work, because push is a macro on the "place",
; and seq isn't the proper "place" to mutate (push will just mutate the local
; binding of seq). This function will actually mutate the cons cell being
; pointed at by seq.
(let (first . rest) seq
(scar seq elt)
(scdr seq (cons first rest)))
seq)
(def insorter (test elt seq)
(if (no seq)
(list elt)
(test elt (car seq))
(destructive-cons elt seq)
(do1 seq
((afn (prev cell)
(if (or (no cell) (test elt (car cell)))
(scdr prev (cons elt cell))
(self cell (cdr cell))))
seq (cdr seq)))))
(mac insorted (test elt seq)
`(zap [insorter ,test ,elt _] ,seq))
Writing it made me realize the issues with making a "truly destructive" insort:
arc> (= y (= x '(1 3 5)))
(1 3 5)
arc> (insorted < 2 x)
(1 2 3 5)
arc> x
(1 2 3 5)
arc> y
(1 2 3 5)
arc> (insorted < 0 x)
(0 1 2 3 5)
arc> x
(0 1 2 3 5)
arc> y
(0 1 2 3 5)
arc> (insorted < 6 x)
(0 1 2 3 5 6)
arc> x
(0 1 2 3 5 6)
arc> y
(0 1 2 3 5 6)
arc> (= y (= x nil))
nil
arc> (insorted < 1 x)
(1)
arc> x
(1)
arc> y
nil
No matter how you try, there's still the edge case with nil, on which you can't scar/scdr.
arc> (= x nil)
nil
arc> (scar x 1)
Error: "set-car!: expected argument of type <pair>; given nil"
arc> (scdr x '(2))
Error: "set-cdr!: expected argument of type <pair>; given nil"
So it appears Arc's model of "places" can't really handle references/aliases that well. Indeed, push, swap, rotate, pop, pushnew, pull, togglemem, and the like are "broken", too, if we expect them to modify the _content_ of their arguments instead of simply where the arguments point. E.g.,
arc> (= y (= x '(a b c)))
(a b c)
arc> (= z (= w '(1 2 3)))
(1 2 3)
arc> (swap x w)
(a b c)
arc> x
(1 2 3)
arc> y
(a b c)
arc> z
(1 2 3)
arc> w
(a b c)
In a way, I can see that being the right behavior for swap, but not for insort. I suppose it's at least somewhat consistent: just alter your expectations of "destructive" operators, since they'll all (near as I can tell) just modify where their input "places" are pointing. Not that I'm ready to write off the issue that way, it's just I don't have a solution and think it gets complicated depending on the operators in question. :)
"..just alter your expectations of destructive operators.."
Yeah, I agree with this. I weakly think (without evidence) that arc's destructive ops are more functional, and it encourages a better style to not constantly make assumptions about precisely which cons cells go where.
On the other hand, a set of 'alias-friendly' functions could coexist with the existing primitives.
Hmm, I wonder if the notion of boxes would help build alias-friendly operations.