I've tried writing this macro several different ways but the only one that works so far uses eval. OnLisp mentions several times that its usually a bad sign when eval is called at runtime.
Shouldn't there be a simple, elegant solution to this problem that doesn't necessitate using ccc and return? Maybe I'm resistant to using continuations because I'm still coming to grips with understanding them... but there's alot of code in arc.arc and only one call to ccc.
You can write a tail-recursive version of tokipin's code pretty easily. Personally though I think this could be an addition to the "examples of LOOP" thread from a while back.
(defun wrandf (xs weights)
(loop with r = (random (apply #'+ weights))
for x in xs
for w in weights
for cw = w then (+ cw w)
when (> cw r) return x))
; assumes pair
(defmacro wrand (&rest args)
`(funcall
(wrandf
(list ,@(mapcar (lambda (x) `(lambda () ,(cadr x))) (pair args)))
(list ,@(mapcar #'car (pair args))))))
Besides the other solutions noted that are more "classic" answers to the problem, I think it's important to ask: why does this have to be a macro? Using eval within a macro, in my experience, is often a sign that you don't need it; you want a function instead. I can't see any signs of the need here either. To say nothing of the soundness of the original solution's method, I believe that this would work:
Also: the reason it needs ints is because of the 'rand function. We could also define a rand-float function which creates a random floating point number and use that instead:
the above now works with weight expressions that return real numbers. Also as specified, only the chosen expression is executed; however, all weight expressions are executed.
Ah, I misread that you wanted something akin to random-elt rather than rand-choice -- i.e., you'd want to use this as a control structure, in which case a macro indeed is what you'd need. My bad. That's what I get for commenting on an empty stomach (well, empty brain is more like it, but excuses are entertaining).
This one is very similar to the definition of rand-choice. (Why does the mappend expression work? Aren't the values of the biases unavailable during macro-expansion?)
This version still needs some massaging but it works with non-constant biases:
(mac bias args
(let bs (map car (pair args))
`(let r (rand (+ ,@bs))
(if ,@(let i 0
(rev (accum a
(each c (map cadr (pair args))
(a `(< r (+ ,@(cut bs 0 (++ i)))))
(a c)))))))))
arc> (with (a 1 b 2 c 3) (bias a 'red b 'white c 'blue))
white
Your macro has problems with variable capture and multiple evaluation (see chapters 9-10 of On Lisp). Here's a version that should work properly:
(mac bias args
(w/uniq r
(withs (ws (map car (pair args))
xs (map cadr (pair args))
us (map [uniq] ws))
`(with ,(mappend list us ws)
(let ,r (rand (+ ,@us))
(if ,@(mappend
(fn (u x) `((< (-- ,r ,u) 0) ,x))
us xs)))))))
IMO, though, the use of a macro here is a premature optimization. I think you should try to get a function working first, and then wrap a macro around it if you know that's what you need. See my comment http://arclanguage.org/item?id=7760 for an example of such a wrapper macro (in CL, but the Arc is similar).
I still suggest you take a look at how I do it http://arclanguage.com/item?id=7765 , which (1) avoids multiple evaluation, and (2) avoids variable capture.
(1) is the hard part here, which is why I had to use a list.