Saturday, March 26, 2011

My Extensible Calculator

An alternative title for this post might be My Turing Complete Calculator. As I mentioned previously, I was inspired to become a Lisper after reading Paul Graham's Revenge of the Nerds. In that essay Graham mentioned that he uses the Lisp REPL as a desktop calculator. That made a lot of sense to me because I hate using a mouse to deal with the typical GUI calculator and things like bc are too hard to remember how to use unless you run them every day.

So I started using the Scheme REPL as a calculator. It was a bit of a pain to fire up Scheme every time I wanted to make some calculations but no more so than calling up bc or a GUI calculator. Then one day, for reasons long forgotten, I needed to calculate the harmonic series, Hn = 1/1 + 1/2 + 1/3 + … + 1/n, for various values of n. It suddenly occurred to me that the Scheme REPL was actually an extensible calculator in that I could add any function I wanted even if it wasn't built in. Since then, I've accumulated these functions in a scheme file that gets loaded by a script called calc. Here's the code for calc.

#! /usr/local/bin/guile
-e repl
!#
(use-modules (ice-9 readline))
(load "/Users/jcs/bin/calc.scm")
(activate-readline)
(define repl
  (lambda x ;;to disappear command line arguments
    (let ((exp (readline "calc--> ")))
      (if (or (eof-object? exp) (string=? exp "bye"))
          (format #t "bye\n")
          (begin
            (catch #t
              (lambda ()
                (format #t "~a\n" (eval (read (open-input-string exp))
                                        (interaction-environment))))
              (lambda (key . args)
                (format #t "~s: ~s\n" key args)))
              (repl))))))

The home grown REPL is there because I first did this under PLT Scheme, which had a REPL function you could call. When I switched to Guile, which doesn't have one, I just rolled my own.

The other part of the calculator is my collection of add-on functions. Here's the help function that lists those functions.

(define help
  (lambda ()
    (for-each (lambda (f) (display f) (newline))
              '("! n: n!"
                "c->f t: convert celsius to Farenheit"
                "combo m n: combination of m things n at a time"
                "coprime? n m: are n and m relatively prime?"
                "dec->hex d: convert the decimal number d to hexidecimal"
                "digits n: number of digits in the number n"
                "f->c t: convert Farenheit to celsius"
                "factor n: factor the number n"
                "fibs n: list the first n Fibonacci numbers"
                "hex->dec h: convert hex number h (#xhhhh) to decinal"
                "hn n: harmonic sum of n terms"
                "lg x: an alias for log2"
                "log2 x: logarithm base 2 of x"
                "modinv n p: find the mod p inverse of a (p prime)"
                "next-larger-prime n: find smallest prime >= n"
                "prime? n: is n prime?"
                "primes n: list the first n primes"
                "simpson f a b n: apply Simpson's rule to f between a and b"
                ))
    'Done))

Every time I find myself needing some function that I don't have, I merely code it up in Scheme and add it to calc.scm.

I can call calc from the command line, of course, but I almost never do. Instead I start it in an ansi-term under Emacs. I have that action bound to C-c c so that it's easy to pop into the calculator whenever I need it. That's especially convenient since, like most Emacs users, I always have Emacs running.

3 comments:

  1. hi jcs, would be great to some screenshot of a session.

    i find using REPL in nested syntax lang to be a problem. e.g. say you want to calculate 3+4/5+2.
    Don't you have to write like (+ 3 (/ 4 5) 2)?
    or, does it support algebraic like "3+4/5+2"? but then, when you need some function such as sqare root etc, it's a problem again. How you solve that?

    ReplyDelete
  2. Xah,

    Yes you have to use standard (parenthesized) notation but I don't mind that at all. In some cases it's even better: for instance 1+2+4+5+9+10 would be (+ 1 2 4 5 9 10) which is easier (at least for me) to type. If I need the square root of 2, say, it's just (sqrt 2). How is that any harder than sqrt(2)? But the best thing is that it's extensible. If I need some new function that's not already built in, I just write it and add it to me file of functions.

    If you don't like the lisp syntax, you could do the same thing with, say, python and have infix notation. As usual, the power and extensibility of Emacs means you can have it your way, whatever that way is.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete