[racket] A VAT calculator using Racket web server
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
scenes??
--- begin ---
#lang racket
;;;; This code is available at my cracket main web page - "Carters Racket
library":
;;;; http://code.google.com/p/cracket/
;;;; Download code:
;;;; hg clone https://cracket.googlecode.com/hg/ cracket
;;; required libraries
(require web-server/formlets
web-server/servlet
web-server/servlet-env)
;;; 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)
x
(safely (string->number x))))
(define (as-string x)
(if (string? x)
x
(safely (number->string x))))
(define (get-number bindings sym default)
(if (exists-binding? sym bindings)
(string->number (extract-binding/single sym bindings))
default))
;;; 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"))
(body
(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
#f)
--- end ---