Programming ≈ Fun

Written by Krešimir Bojčić

Lisp Lure Revisited Allais Paradox

One of comments over my last blog post was by Alan Crowe who suggested to checkout usage of macros in his implementation of Allais paradox.

(defun small-win ()
  (format t "~&Congratulations! You win a million dollars."))

(defun big-win ()
  (loop repeat 5 do (small-win)))

(defun no-win ()
  (format t "~&Shrug. You were not likely to win and you didn't."))

(defun big-regret ()
  (format t "~&What a loser! You could have had a million dollars."))

;;; A recursive function that turns a,b,c into a, a+b, a+b+c.
(defun accumulate (numbers total)
  (if (endp numbers)
      '()
      (let ((more (+ (first numbers) total)))
        (cons more
              (accumulate (rest numbers)
                          more)))))

;;; Common Lisp code that is incomprehensible to mere mortals :-)
(defun make-guarded-commands (die-var)
  (lambda (clause bound)
    `((< ,die-var ,bound) ,@(last clause))))

(defmacro percentage-case (&body clauses)
  (dolist (clause clauses)
    (check-type (first clause) integer))
  (assert (= 100
             (reduce #'+ clauses
                     :key #'first)))
  (let ((cumulative-distribution (accumulate (mapcar #'first clauses) 0))
        (die-roll (gensym)))
    `(let ((,die-roll (random 100)))
           (cond ,@(mapcar (make-guarded-commands die-roll)
                     clauses
                     cumulative-distribution)))))


;;; Code for all to read

;;; The point of the macro definition was to permit the
;;; four options at
;;; http://www.thebigquestions.com/2010/10/20/the-noble-savage
;;; to be coded cleanly

(defun 1a ()
  (small-win))

(defun 1b ()
  (percentage-case
    (89 (small-win))
    (10 (big-win))
    (1 (big-regret))))

(defun 2a ()
  (percentage-case
    (11 (small-win))
    (89 (no-win))))

(defun 2b ()
  (percentage-case
    (10 (big-win))
    (90 (no-win))))

Main usage is to extend syntax of a language to accommodate for this problem in order to trivialize code that has to be written in order to describe it. As you can see from code above he is a fine gentelman and a scholar. It took me over an hour to figure out what is going on. I attribute that to my non existent Lisp skills. Truth be told code itself is pretty slick. Would I feel smug about myself if I had ability to write code like that? You bet! On the other hand it feels like overkill to extend language syntax for this kind of problem.

I tried to emulate this way of solving problem (calculating distribution with accumulate, and creating lambda expression that gets evaluated for random number…) and it was a mess. Lisp has way more tools at disposal for that kind of thinking.

After a while I gave up on emulating Lisp and took what I got. This is what I’ve come up with, bare in mind that I have at best lower-end-mediocre-ruby-skills, so I bet this could be done simpler.

def small_win() print "You've won million dollars.\n" end
def big_win() 5.times { small_win } end
def big_regret() print "What a greedy loser!.\n" end
def no_win() print "You were not likely to win and you didn't.\n" end

def role_the_dice(distribution)
  raise "Distribution percentages must add up to 100%" unless
     distribution.each_value.reduce(:+) == 100
  random, current = Random.rand(100), 0
  distribution.each_pair { |key, value|
    send key if (current..(current += value)).include?(random) }
end

#options coded cleanly
def a1() role_the_dice( small_win: 100 ) end
def b1() role_the_dice( small_win: 89, big_win: 10, big_regret: 1 ) end
def a2() role_the_dice( small_win: 11, no_win: 89 ) end
def b2() role_the_dice( big_win: 10, no_win: 90 ) end

User API is equally simple, and I would much rather debug Ruby over Lisp version couple of months later. That leaves the same problem open. I am having a really hard time finding example where Lisp shines with his macro-fu over other contenders.

I agree that when you need to extend syntax of a language than Lisp is a way to go. Problem is that I think those kind of situations are rare enough not to justify sitting in S-expressions and close to the AST all day long waiting for need to extend syntax to emerge. It’s good to know that you don’t have to wait for 2 years (Java - Siebel) to extend language if you need to, but real need is not that often.

That leaves me with feature that is cool but not that usable, and some “basic” stuff missing because of lack of libraries. Further complication is that Ruby with blocks does enable you to extend language features. It still has to be Ruby, but all kind of stuff can be done. (Smalltalk that was able to get away with only six keywords is prove of that).

Comments