BIG WARNING: I've never really run, read, or modified news.arc thoroughly. This is as much as I can gather from just looking.
There are several ways you might login in news.arc, and correspondingly, there are specific bits of code for each of those cases: when you click the top-right login link (see topright in news.arc), when you try to vote (see newsop vote), when you try to submit (see submit-login-warning), when you try to comment (see comment-login-warning), when you try to reply (see newsop reply), etc.
What do these have in common? The login-page function, defined in app.arc:
(def login-page (switch (o msg nil) (o afterward hello-page))
(whitepage
(pagemessage msg)
(when (in switch 'login 'both)
(login-form "Login" switch login-handler afterward)
(hook 'login-form afterward)
(br2))
(when (in switch 'register 'both)
(login-form "Create Account" switch create-handler afterward))))
In each context, different things happen with the afterward parameter passed to login-page. topright logs that the user signed in via the topright link, newsop vote registers the user's vote after they log in, submit-login-warning redirects the user to the submit page, etc. Since login-page is the single point of entry, you might change it. However, there's this comment in news.arc:
; Need this because can create users on the server (for other apps)
; without setting up places to store their state as news users.
; See the admin op in app.arc. So all calls to login-page from the
; news app need to call this in the after-login fn.
(def ensure-news-user (u)
(if (profile u) u (init-user u)))
Aha. This whittles down a single-point of entry specifically for news.arc logins, since they all need to call ensure-news-user in the afterwards parameter of login-page. So, it makes more sense to change this.
You still need to make sure ensure-news-user behaves the same way (i.e., returns the proper user) so that the functions that use ensure-news-user don't break. Shouldn't be too bad to add a ++ in there, then just return the user.
I'll interpret "it is a new day" as "it's a new day between now and when they last logged in" (which isn't necessarily 24 hours, just a new date). This requires knowing when they last logged in. Probably the easiest way to get this would be adding some new info to the profile template, and recording it on each login (an action we're doing now anyways, so not a big deal). Look for deftem profile and add a line to it:
(deftem profile
id nil
name nil
created (seconds)
...
last-login (date) ; default last-login = whenever the new profile was made
...
delay 0)
Now all you need to do is record their last login in ensure-news-user and do some date arithmetic to see if it's a "new day".
; These two functions take in the today argument (i.e., (date)), since
; otherwise you might do the equivalent of
;
; (it-is-a-new-day u (date))
; ...
; (update-last-login u (date))
;
; But (date) might change between the times when you make the two function
; calls, so it should be bound outside, like
;
; (let today (date)
; (it-is-a-new-day u today)
; ...
; (update-last-login u today))
(def it-is-a-new-day (u today)
(with ((new-y new-m new-d) today
(old-y old-m old-d) (uvar u last-login))
(or (> new-d old-d) ; e.g., Apr 15, 2010 -> Apr 16, 2010
(> new-m old-m) ; e.g., Apr 30, 2010 -> May 01, 2010
(> new-y old-y)))) ; e.g., Dec 31, 2010 -> Jan 01, 2011
(def update-last-login (u today)
(= (uvar u last-login) today))
(let old ensure-news-user
(def ensure-news-user (u)
(with (u (old u) today (date))
(when (it-is-a-new-day u today)
(++ (karma u)))
(update-last-login u today))
u)) ; return the result of old-ensure-news-user
Per my warning, this is untested. Someone else is probably more qualified to gauge the quality of this response. Alternatively, you could increase everyone's karma as a batch job that runs every midnight, but that removes the login part of your requirement. Good luck!
Just one question: Is their karma supposed to be increased every time the log in and it's a new day, or every time they are logged in and it's a new day? I ask because I almost never have to log in to the arc forum; my session apparently lasts a long time, possibly lasting until I log in from a different computer or longer. As such, I never actually go through the official log in process, but I'm still online.
Does ensure-news-user get run on page load as part of the code that checks whether their logged in or not? From a cursory look at the code, it didn't appear to work that way.
How would you write it differently if you wanted to increase their karma for every day they logged on, as opposed to for day they logged in?
Perhaps we need to make the is parameterizable as well in the general case. Hmm, this could be a generalization of my firsttime (http://arclanguage.org/item?id=12889)
(firsttime user!loggedin
(prn *welcome-message*))
(updating (uvar u last-login) (date)
:body
(++ karma.u))
; track largest value seen so far
(ret max 0
(each elem '(1 3 2 6 5)
(updating max :iff > elem
(prn "max now: " max))))
max now: 1
max now: 3
max now: 6
6
Hmm, I might get rid of firsttime altogether. Is updating the right name for this macro?
You're evaluating the subexpressions of 'place twice, which isn't necessary thanks to 'setforms. Also, I like to have macros macro-expand and evaluate their parameters from left to right if I can, just for similarity with 'do. (I still try to evaluate only as much as I need to though.) To accomplish these things, I'd go with this (untested) implementation instead:
In the meantime, for the purposes of dates, I'd at least use 'iso instead of 'is. You're probably not going to get the same (date) list as one you have from earlier today. Even if (date) itself made that guarantee, you've potentially persisted and un-persisted the user data today.
In fact, since you've already made 'iso extensible, I'd use it as the default instead of 'is.
Saying (do.foo ...) instead of (foo ...) just makes it so that 'foo isn't in function position. That means the local variable 'foo will always be used instead of some macro named 'foo that happened to be defined in another library.
The ssyntaxes .foo and !foo are short for get.foo and get!foo. Also, a:b ssyntax is handled before a.b ssyntax, giving the : lower precedence.
That means (.new-val:do.setter-getter) compiles as though it's ((get new-val) ((do setter-getter))), which makes it a roundabout way to accomplish ((do.setter-getter) new-val) with one less pair of parentheses, one less character, or (by my count) one less token.
It's really sort of silly in this case, since you save on so little while rearranging so much. In its favor, he (.a:b ...) idiom makes a bit more sense when it's a field/element access like (!width:get-rect ...) or (!2:setforms ...). It's especially helpful when it's part of a big chain of composed forms like (.a:b:.c:.d:e ...).
In my own code, including Lathe, I define this:
(def call (x . args)
(apply x args))
Then the code is just call.setter-getter.new-val, giving a savings of two characters, two pairs of parentheses, or (by my count) four tokens, all without causing the expressions to be rearranged.
Saying (do.foo ...) instead of (foo ...) just makes it so that 'foo isn't in function position. That means the local variable 'foo will always be used instead of some macro named 'foo that happened to be defined in another library.
Not in Arc. The best you get is (.g:.d:.c:a "b"), I think. You can shove "b" into ssyntax with string!b, but you need to use it as an argument somehow, and any ssyntax containing .string!b will pass the 'string function itself as an argument instead of calling it first.
Of course, if "b" were a symbol instead of a string, it would just be "a!b.c.d.g".
In Penknife, the answer to your question is actually yes: It's "q.b'a.c.d.g", where q is the string-quoting operator and ' is an operator that acts like a reverse a.b. It's easy to stump Penknife too, but I'm hoping to make a full thread about Penknife's take on infix operators in a couple of days or so.
arc> (= test (obj "a" "my a val" "b" "my b val"))
#hash(("a" . "my a val") ("b" . "my b val"))
arc> test%a
"my a val"
[edit: actually, it would be nicer to have the percent symbol represent the spaces in the string and have some other symbol signify string handling, but I never got around to it + my scheme foo is lacking :)]
Wow. Thanks for such a thorough response! Funny, I had 'last-log' in the deftem profile, and was tweaking ensure-news-user. I couldn't get it working, but's nice to know I was tweaking in the right places. BTW, your interpretation of what I meant by 'new day' was spot on.
I am going to spend some time trying this out. Thank you. I'll let you know how it goes.
Thanks again. Based on the login issue shader pointed out, I decided to change things up a bit, and ++ karma if it is a new day, and the user comments or replies. This is what I did, and it seems to work. I started with process-comment:
Arc has a feature called "ssyntax", short for symbol syntax, that allows us to use certain characters in a symbol as shorthand for a commonly used longer form.
In this case, "karma.u" expands to (karma u), which is a macro that looks up the users karma. Functions applied to single arguments are extremely common in arc, so it is quite useful to have an abbreviation.
Other ssyntax include:
a!b -> (a 'b)
~a -> (no a)
a~b -> (compose a (no b))
a:b -> (compose a b) ;(a:b c) is equivalent to (a (b c))