Arc Forumnew | comments | leaders | submitlogin
3 points by thaddeus 5128 days ago | link | parent

Great response - as always!

the date comparison could be easier:

  (deftem profile
      ...
      last-login (datestring))

   (let old ensure-news-user
      (def ensure-news-user (u)
        (with (u (old u) today (datestring))
          (when (> today (uvar u last-login))
            (++ (karma u)))
          (update-last-login u today))
          u))


1 point by akkartik 5127 days ago | link

Since time always increases, you can also just:

  (unless (is (date) (uvar u last-login))
    (++ karma.u)
    (= (uvar u last-login) (date)))
update: Hmm, I just realized there's a few places I can use this macro:

  (mac updating(place expr . body)
    (w/uniq rhs
      `(let ,rhs ,expr
         (unless (is ,place ,rhs)
           (= ,place ,rhs)
           ,@body))))

  ..
  (updating (uvar u last-login) (date)
    (++ karma.u))
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)

  (mac updating(place ? expr t iff 'is . body)
    (w/uniq rhs
      `(let ,rhs ,expr
         (unless (,iff ,place ,rhs)
           (= ,place ,rhs)
           ,@body))))

  (mac firsttime(place . body)
    `(updating ,place
        :body
          ,@body))
(requires my keyword args and new optional syntax: http://github.com/akkartik/arc)

Now you can run several kinds of test-and-update:

  (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?

-----

2 points by rocketnia 5127 days ago | link

For this:

  (mac updating(place ? expr t iff 'is . body)
    (w/uniq rhs
      `(let ,rhs ,expr
         (unless (,iff ,place ,rhs)
           (= ,place ,rhs)
           ,@body))))
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:

  (def fn-updating (current-val setter-getter new-val comparator body)
    (unless (do.comparator current-val new-val)
      (.new-val:do.setter-getter)
      (do.body)))
  
  (mac updating (place ? expr t iff 'is . body)
    (let (binds val setter) setforms.place
      `(withs ,binds
         (fn-updating ,val (fn () ,setter) ,expr ,iff (fn () ,@body)))))
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.

-----

1 point by akkartik 5127 days ago | link

All great points, thanks! I have in fact stopped using is everywhere in my code, don't know what I was thinking.

-----

1 point by akkartik 5127 days ago | link

Why the do's?

And what does the .new-val do?

-----

3 points by rocketnia 5127 days ago | link

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.

Seems like a lot of brevity boilerplate, huh? :-p

-----

2 points by aw 5127 days ago | link

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.

I remembered talking about this once and found the comment: http://arclanguage.org/item?id=11697

It looks like it would be an easy change to Arc to have local variables take precedence, though I haven't tried the patch myself.

-----

1 point by akkartik 5127 days ago | link

Oh nice. So is there a way using this trick to do

  a."b".c.d.g
without parens?

-----

3 points by rocketnia 5127 days ago | link

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.

-----

2 points by thaddeus 5124 days ago | link

Optionally, in ac.scm:

  (define (has-ssyntax-char? string i)
    (and (>= i 0)
         (or (let ((c (string-ref string i)))
               (or (eqv? c #\:) (eqv? c #\~) 
                   (eqv? c #\&)                
                   (eqv? c #\%)                
                   ;(eqv? c #\_) 
                   (eqv? c #\.)(eqv? c #\!)))
             (has-ssyntax-char? string (- i 1)))))

  (define (expand-ssyntax sym)
    ((cond ((or (insym? #\: sym) (insym? #\~ sym)) expand-compose)
           ((or (insym? #\. sym) (insym? #\! sym)(insym? #\% sym)) expand-sexpr)
           ((insym? #\& sym) expand-and)
       ;   ((insym? #\_ sym) expand-curry)
           (#t (error "Unknown ssyntax" sym)))
   sym))

  (define (build-sexpr toks orig)
    (cond ((null? toks)
           'get)	    
          ((null? (cdr toks))
           (chars->value (car toks)))
          (#t
           (list (build-sexpr (cddr toks) orig)
                 (cond ((eqv? (cadr toks) #\!)
                        (list 'quote (chars->value (car toks))))
                       ((eqv? (cadr toks) #\%)
                         (list 'string (list 'quote (chars->value (car toks)))))
                       ((or (eqv? (car toks) #\.)(eqv? (car toks) #\!)(eqv? (car toks) #\$)(eqv? (car toks) #\%))
                         (err "Bad ssyntax" orig))
                       (#t 
                       (chars->value (car toks))))))))
Then:

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

-----

1 point by akkartik 5127 days ago | link

Is fn-updating generally useful?

-----