You should try to separate the logic of your code from input/output operations, like asking for numbers or writing results. It is often best to just write functions that are given numbers and output numbers. Then, you can call it from any kind of user-interface, graphical or textual.
Business logic
You want to compute the gross pay given a number of work hours and an hourly rate.
So you define a function, named gross-pay
, which takes as input a number of work-hours
, and a hourly-rate
:
(defun gross-pay (work-hours hourly-rate)
10000 ;; a lot of money, just for testing
)
Now that it is defined, you can call it, and your environment will print the result automatically:
* (gross-pay 0 0)
10000
One of the problems in your code is that you have an if
branch with one test and three branches, which is not a valid syntax. Based on what you wrote, I guess you wanted to have this logic:
(defun gross-pay (work-hours hourly-rate)
(assert (<= 0 work-hours 60))
;; ...
)
The assert
checks if the condition holds, and signals an error otherwise. I think there is no valid computation in case the number of hours is below zero or greater than 60, in which case it makes sense to have an error.
Then, you want to compute the gross-pay, with the formula you gave.
You have to compute the number of regular hours, the number of over-time, and price both kinds of hours differently (and sum them).
Let's define an auxiliary function, split-time
, which returns two values, the amount of hours below or equal to 40, and the remaining time (over 40):
(defun split-time (hours)
(if (> hours 40)
;; the regular amount is maxed to 40
;; overtime is the rest
(values 40 (- hours 40))
;; regular amount is the same as the input
;; no overtime
(values hours 0)))
You can get the result of multiple values
by using multiple-value-bind
, as follows:
(defun gross-pay (work-hours hourly-rate)
(assert (<= 0 work-hours 60))
(multiple-value-bind (regular overtime) (split-time work-hours)
;; here regular and overtime are bound to values
))
Finally:
(defun gross-pay (work-hours hourly-rate)
(assert (<= 0 work-hours 60))
(multiple-value-bind (regular overtime) (split-time work-hours)
(+ (* regular hourly-rate)
(* overtime hourly-rate 3/2))))
Here, I am using 3/2
instead of 1.5
because floating-point computation is approximate, but rational numbers are precise. The result can be converted back to a float when printing it, for example.
Tests
Now that you have a function, you can test it:
* (gross-pay 10 1)
10
* (= (gross-pay 50 1)
(+ (* 40 1)
(* 10 3/2)))
T
Input/output
You can wrap the logic in command-line interface if you want:
(defun gross-pay/io ()
(gross-pay
(ask "Enter working hours: " '(integer 0 60))
(ask "Enter hourly pay rate: " '(integer 1 1000))))
The above works thanks to this helper function, which takes care of different issues that may arise when asking for inputs (clearing buffering, forcing output, disabling evaluation when reading values, checking for a type, etc.)
(defun ask (prompt type)
(let ((*standard-output* *query-io*))
(loop
(clear-input)
(fresh-line)
(princ prompt)
(finish-output)
(let ((value (let ((*read-eval* nil)) (read))))
(if (typep value type)
(return value)
(format *error-output*
"type mismatch: ~a not of type ~a"
value type))))))
In my environment, here is an example interaction:
USER> (gross-pay/io)
Enter working hours: 5
Enter hourly pay rate: 3
15
And an example of bad input:
Enter working hours: 87
type mismatch: 87 not of type (INTEGER 0 60)
Enter working hours: ""
type mismatch: not of type (INTEGER 0 60)
Enter working hours: 15
Enter hourly pay rate: 0
type mismatch: 0 not of type (INTEGER 1 1000)
Enter hourly pay rate: 88
1320