[racket] A VAT calculator using Racket web server

From: Mark Carter (mcturra2000 at yahoo.co.uk)
Date: Mon Sep 6 06:48:09 EDT 2010

So, I'm learning the ropes of using Racket as a webserver. I'm building a simple 
VAT (tax) calculator. I present my code at the end of this post (which you can 
also download via google code). 

I think it would be helpful to me if you could critique it a little.

The idea is that you point your browser to the page. It has some default values 
if you start it from afresh. You can enter some values yourself, press the 
Calculate button, and it calls the page again  using your entered values; and 
performs a few calculations.

The questions I'd like to ask specifically at this point are:
* should state (in my case the user's input for amount and tax rate) be passed 
as a separate parameter in the function definition, or is it better to pack it 
in with the request?
* is there a better way of writing button-clicked? The function is doing a lot 
of work of hand-decoding the request and repacking it - that seems inelegant
* I'm a little puzzled as to what the deal is with the parameter make-url. I'm 
thinking that has something to do with creating the "continuation" behind the 

--- begin ---
#lang racket

;;;; This code is available at my cracket main web page - "Carters Racket 
;;;;     http://code.google.com/p/cracket/
;;;; Download code:
;;;;     hg clone https://cracket.googlecode.com/hg/ cracket

;;; required libraries
(require web-server/formlets

;;; some helper functions

(define-syntax catch-errors
  (syntax-rules ()
          ((_ error-value body ...)
           (with-handlers ([exn:fail? (lambda (exn) error-value)])
             body ...))))

(define-syntax safely 
  (syntax-rules ()
    ((_ body ...)
     (catch-errors "Error" body ...))))

(define (as-number x)
  (if (number? x)
      (safely (string->number x))))
(define (as-string x)
  (if (string? x)
      (safely (number->string x))))

(define (get-number bindings sym default)
  (if (exists-binding? sym bindings)
      (string->number (extract-binding/single sym bindings))

;;; our "VAT" web-page itself
;;; STATE is whatever we choose to manage outselves, in whatever form.

(define (vat-page state request) 
  (define amount (first state))
  (define rate (second state))

  (define (button-clicked request)
    (define bindings (request-bindings request))
    (define amount (get-number bindings 'amount 100))
    (define rate (get-number bindings 'rate 17.5))
    (vat-page (list amount rate) (redirect/get)))
  (define (response-generator make-url)    
    ;; perform calculations
    (define net amount)
    (define vat (safely (/ (* rate net) 100.0)))
    (define gross (safely (+ net vat)))
    (define net-string (as-string net))
    (define vat-string (as-string vat))
    (define gross-string (as-string gross))
    ;; return our rendering of the results
    `(html (head (title "VAT Calculator"))
            (h1 "Simple VAT calculator")
            (h2 "Inputs")
            (form ([action ,(make-url button-clicked)])
                  (table (tbody
                          (tr (td "Amount:") (td (input [ (type "text") (name 
"amount") (value ,(as-string amount)) ]) ))
                          (tr (td "VAT Rate:") (td (input [ (type "text") (name 
"rate") (value ,(as-string rate)) ]) ))))               

                  (input [(type "submit") (value "Calculate")]))
            (h2 "Outputs")
            (table (tbody
                    (tr (td "Net:") (td ,net-string))
                    (tr (td "VAT:") (td ,vat-string))
                    (tr (td "Gross:") (td ,gross-string))))
            (p "Enjoy!"))))
  (send/suspend/dispatch response-generator))

;;; Start the server
(serve/servlet (lambda (x) (vat-page (list 100 17.5) x)) #:port 80 #:listen-ip 
--- end ---


Posted on the users mailing list.