[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.lisp

Learning Lisp: Now Stumped...

Tim Hawes

12/25/2015 8:50:00 PM

I am working through a book of programming exercises in attempts to learn Common Lisp. I am writing a simple tip calculator. You put in a bill amount, and tip percentage, and it will calculate the total. Within that specification, it is suppose to not allow negative numbers for entry. I am using a parse-float function, for extracting floats from a string, that I found posted in this newsgroup a few years ago, and Peter Seibel's read-prompt function from his book "Practical Common Lisp".

The function enter-bill-amount, I have:

(defun enter-bill-amount ()
"Enter the bill amount."
(let ((bill-amount nil))
(loop
(if (or (not bill-amount) (and bill-amount (< bill-amount 0)))
....)))

It is suppose to continue the loop and re-prompt the user if he enters anything other than a positive number. This function works as expected.

I have a similar thing for enter-tip-rate:

(defun enter-tip-rate ()
"Enter tip rate."
(let ((tip-rate nil))
(loop
(if (or (not tip-rate) (and tip-rate (< tip-rate 0)))
....)))

The most notable difference in behavior is that enter-bill-amount will keep re-prompting the user for input if the user just hits enter, while the enter-tip-rate with just go with a default value of 15% if the user just hits enter.

In SLIME, SBCL tells me, when trying to compile enter-tip-rate:

; (AND TIP-RATE (< TIP-RATE 0))
; --> IF
; ==>
; TIP-RATE
;
; note: deleting unreachable code

; (< TIP-RATE 0)
; ==>
; TIP-RATE
;
; note: deleting unreachable code
;
; compilation unit finished
; printed 3 notes

It will run the function, but alas, it does ignore the (and tip-rate (< tip-rate 0))) part and allows negative numbers. Why is this code unreachable?

Full code is here:

http://pastebin.co...
20 Answers

Lieven Marchand

12/25/2015 9:28:00 PM

0

Tim Hawes <trhawes@gmail.com> writes:

> Full code is here:
>
> http://pastebin.co...

(return) doesn't do what you think it does.

--
Although the invention of computer science has presented several inconveniences
to mankind - ask ordinary Iraqis what they think of the computers on a cruise
missile -, it offers a great advantage to mathematical pedagogy: a proof is
totally correct if a suitably programmed computer could understand it. Godement

Pascal J. Bourguignon

12/25/2015 9:58:00 PM

0

Tim Hawes <trhawes@gmail.com> writes:

> I am working through a book of programming exercises in attempts to
> learn Common Lisp. I am writing a simple tip calculator. You put in a
> bill amount, and tip percentage, and it will calculate the
> total. Within that specification, it is suppose to not allow negative
> numbers for entry. I am using a parse-float function, for extracting
> floats from a string, that I found posted in this newsgroup a few
> years ago, and Peter Seibel's read-prompt function from his book
> "Practical Common Lisp".
>
> The function enter-bill-amount, I have:
>
> (defun enter-bill-amount ()
> "Enter the bill amount."
> (let ((bill-amount nil))
> (loop
> (if (or (not bill-amount) (and bill-amount (< bill-amount 0)))
> ....)))
>
> It is suppose to continue the loop and re-prompt the user if he enters
> anything other than a positive number. This function works as
> expected.
>
> I have a similar thing for enter-tip-rate:
>
> (defun enter-tip-rate ()
> "Enter tip rate."
> (let ((tip-rate nil))
> (loop
> (if (or (not tip-rate) (and tip-rate (< tip-rate 0)))
> ....)))
>
> The most notable difference in behavior is that enter-bill-amount will
> keep re-prompting the user for input if the user just hits enter,
> while the enter-tip-rate with just go with a default value of 15% if
> the user just hits enter.


(or (not tip-rate) (and tip-rate (< tip-rate 0)))

In CL, AND and OR are short-circuiting. So you can write just:

(or (null tip-rate) (minusp tip-rate))

Notice that NOT and NULL are identical, but you would use NOT on boolean
values, while you would use NULL to test for NIL. (To test an empty
list, you would use ENDP, but it's not identical to NULL: ENDP signals
an error if you give it a non-NIL atom).

Here, we would consider that tip-rate is of type (or null real), not of
type (or boolean real), since it wouldn't make any sense to assign true
(T or some other true value) to tip-rate.


> In SLIME, SBCL tells me, when trying to compile enter-tip-rate:
>
> ; (AND TIP-RATE (< TIP-RATE 0))
> ; --> IF
> ; ==>
> ; TIP-RATE
> ;
> ; note: deleting unreachable code

Hence the deleting unreachable code you have here (you tested twice
tip-rate in your OR).


> ; (< TIP-RATE 0)
> ; ==>
> ; TIP-RATE
> ;
> ; note: deleting unreachable code
> ;
> ; compilation unit finished
> ; printed 3 notes
>
> It will run the function, but alas, it does ignore the (and tip-rate
> (< tip-rate 0))) part and allows negative numbers. Why is this code
> unreachable?

Hopefully, it doesn't ignore (< tip-rate 0), only the (and tip-rate)
part.

> Full code is here:
>
> http://pastebin.co...

(or (parse-float (prompt-read "Enter bill amount"))
nil)

the OR is useless here. There is only one value that is false, this is
NIL, so the only way to go to the alternative of this OR is for
parse-float to return NIL, which is already the same value as what you
have in the alternative. So it's useless.

(parse-float (prompt-read "Enter bill amount"))

would be just good.



(format *query-io* "~a: " prompt)

You may use ~& to ensure that a newline is inserted when needed:

(format *query-io* "~&~a: " prompt)




(setf bill-amount (read-from-string (format nil "~$" bill-amount)))

Very not a nice way to do it. You may use ROUND and adjust it back
again:

(let ((sub-devise 0.01D0))
(setf bill-amount (* (round bill-amount sub-devise) sub-devise)))



For more advanced Common Lisp tools to do something similar, have a look
at RESTART-CASE (clsh restart-case has a nice example).

In simple cases, you may also use CHECK-TYPE. Try:

(let ((price -42.12))
(check-type price (real 0 200))
price)



(zerop (length d))

Not a good idea to do that raw: people^W users are known to type
whitespaces in empty fields, so at the very least, you want to do:

(zerop (length (string-trim #(#\space #\tab) d)))



(when (zerop (length d))
(progn
(setf tip-rate 15)
(return)))

PROGN is useless here, WHEN already have an implicit PROGN:

(when (zerop (length d))
(setf tip-rate 15)
(return))

You could use the complex form of LOOP:

(defun enter-tip-rate ()
"Enter tip rate."
(loop
:with tip-rate := nil
:while (or (null tip-rate) (minusp tip-rate))
:do (let ((line (prompt-read "Enter the tip rate (without the %, default is 15)")))
(when (zerop (length (string-trim #(#\space #\tab) line)))
(setf tip-rate 15)
(loop-finish))
(setf tip-rate (parse-float line))
(when tip-rate
(loop-finish))
(format t "That is not a valid amount! Digits only, please.~%"))
:finally (return (/ tip-rate 100.0))))

--
__Pascal Bourguignon__ http://www.informat...
â??The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.� -- Carl Bass CEO Autodesk

William James

12/25/2015 10:27:00 PM

0

Pascal J. Bourguignon wrote:

> Tim Hawes <trhawes@gmail.com> writes:
>
> > I am working through a book of programming exercises in attempts to
> > learn Common Lisp. I am writing a simple tip calculator. You put in a
> > bill amount, and tip percentage, and it will calculate the
> > total. Within that specification, it is suppose to not allow negative
> > numbers for entry. I am using a parse-float function, for extracting
> > floats from a string, that I found posted in this newsgroup a few
> > years ago, and Peter Seibel's read-prompt function from his book
> > "Practical Common Lisp".
> >
> > The function enter-bill-amount, I have:
> >
> > (defun enter-bill-amount ()
> > "Enter the bill amount."
> > (let ((bill-amount nil))
> > (loop
> > (if (or (not bill-amount) (and bill-amount (< bill-amount 0)))
> > ....)))
> >
> > It is suppose to continue the loop and re-prompt the user if he enters
> > anything other than a positive number. This function works as
> > expected.
> >
> > I have a similar thing for enter-tip-rate:
> >
> > (defun enter-tip-rate ()
> > "Enter tip rate."
> > (let ((tip-rate nil))
> > (loop
> > (if (or (not tip-rate) (and tip-rate (< tip-rate 0)))
> > ....)))
> >
> > The most notable difference in behavior is that enter-bill-amount will
> > keep re-prompting the user for input if the user just hits enter,
> > while the enter-tip-rate with just go with a default value of 15% if
> > the user just hits enter.
>
>
> (or (not tip-rate) (and tip-rate (< tip-rate 0)))
>
> In CL, AND and OR are short-circuiting. So you can write just:
>
> (or (null tip-rate) (minusp tip-rate))
>
> Notice that NOT and NULL are identical, but you would use NOT on boolean
> values, while you would use NULL to test for NIL. (To test an empty
> list, you would use ENDP, but it's not identical to NULL: ENDP signals
> an error if you give it a non-NIL atom).
>
> Here, we would consider that tip-rate is of type (or null real), not of
> type (or boolean real), since it wouldn't make any sense to assign true
> (T or some other true value) to tip-rate.
>
>
> > In SLIME, SBCL tells me, when trying to compile enter-tip-rate:
> >
> > ; (AND TIP-RATE (< TIP-RATE 0))
> > ; --> IF
> > ; ==>
> > ; TIP-RATE
> > ;
> > ; note: deleting unreachable code
>
> Hence the deleting unreachable code you have here (you tested twice
> tip-rate in your OR).
>
>
> > ; (< TIP-RATE 0)
> > ; ==>
> > ; TIP-RATE
> > ;
> > ; note: deleting unreachable code
> > ;
> > ; compilation unit finished
> > ; printed 3 notes
> >
> > It will run the function, but alas, it does ignore the (and tip-rate
> > (< tip-rate 0))) part and allows negative numbers. Why is this code
> > unreachable?
>
> Hopefully, it doesn't ignore (< tip-rate 0), only the (and tip-rate)
> part.
>
> > Full code is here:
> >
> > http://pastebin.co...
>
> (or (parse-float (prompt-read "Enter bill amount"))
> nil)
>
> the OR is useless here. There is only one value that is false, this is
> NIL, so the only way to go to the alternative of this OR is for
> parse-float to return NIL, which is already the same value as what you
> have in the alternative. So it's useless.
>
> (parse-float (prompt-read "Enter bill amount"))
>
> would be just good.
>
>
>
> (format *query-io* "~a: " prompt)
>
> You may use ~& to ensure that a newline is inserted when needed:
>
> (format *query-io* "~&~a: " prompt)
>
>
>
>
> (setf bill-amount (read-from-string (format nil "~$" bill-amount)))
>
> Very not a nice way to do it. You may use ROUND and adjust it back
> again:
>
> (let ((sub-devise 0.01D0))
> (setf bill-amount (* (round bill-amount sub-devise) sub-devise)))
>
>
>
> For more advanced Common Lisp tools to do something similar, have a look
> at RESTART-CASE (clsh restart-case has a nice example).
>
> In simple cases, you may also use CHECK-TYPE. Try:
>
> (let ((price -42.12))
> (check-type price (real 0 200))
> price)
>
>
>
> (zerop (length d))
>
> Not a good idea to do that raw: people^W users are known to type
> whitespaces in empty fields, so at the very least, you want to do:
>
> (zerop (length (string-trim #(#\space #\tab) d)))
>
>
>
> (when (zerop (length d))
> (progn
> (setf tip-rate 15)
> (return)))
>
> PROGN is useless here, WHEN already have an implicit PROGN:
>
> (when (zerop (length d))
> (setf tip-rate 15)
> (return))
>
> You could use the complex form of LOOP:
>
> (defun enter-tip-rate ()
> "Enter tip rate."
> (loop
> :with tip-rate := nil
> :while (or (null tip-rate) (minusp tip-rate))
> :do (let ((line (prompt-read "Enter the tip rate (without the %, default is 15)")))
> (when (zerop (length (string-trim #(#\space #\tab) line)))
> (setf tip-rate 15)
> (loop-finish))
> (setf tip-rate (parse-float line))
> (when tip-rate
> (loop-finish))
> (format t "That is not a valid amount! Digits only, please.~%"))
> :finally (return (/ tip-rate 100.0))))

MatzLisp (Ruby):

def get_float default=nil
s = $stdin.gets.strip
return default if s.empty?
s.match( /^(?=.*\d)\d*[.]?\d*$/ ) and s.to_f
end

def get_bill_amount
loop {print "Enter amount of bill: "
if x = get_float and x >= 0.0
return x
else
puts "Invalid amount."
end}
end

def get_tip
loop {print "Enter percentage for tip (without %): "
if x = get_float(15.0) and x >= 0.0
return x/100
else
puts "Invalid tip percentage."
end}
end

get_tip
Enter percentage for tip (without %): 12
==>0.12
get_tip
Enter percentage for tip (without %):
==>0.15
get_tip
Enter percentage for tip (without %): 2.3.4
Invalid tip percentage.
Enter percentage for tip (without %): .
Invalid tip percentage.
Enter percentage for tip (without %): 9.5
==>0.095
get_tip
Enter percentage for tip (without %): 22
==>0.22


--
[A]daptive behavior and group-identifications of gentiles were pathologized
while Jewish group identification, ingroup pride, family pride, upward social
mobility, and group continuity retained their psychological importance and
positive moral evaluation. --- Kevin MacDonald; "The Frankfurt School of Social
Research and the Pathologization of Gentile Group Allegiances"

Tim Hawes

12/25/2015 11:42:00 PM

0

On Friday, December 25, 2015 at 4:30:28 PM UTC-5, Lieven Marchand wrote:
> (return) doesn't do what you think it does.


I think it breaks out of loop. Does it do something else?

Tim Hawes

12/26/2015 12:06:00 AM

0

Thanks for all your helpful information.

On Friday, December 25, 2015 at 4:57:51 PM UTC-5, informatimago wrote:
> (or (not tip-rate) (and tip-rate (< tip-rate 0)))
>
> In CL, AND and OR are short-circuiting. So you can write just:
>
> (or (null tip-rate) (minusp tip-rate))
>
> Notice that NOT and NULL are identical, but you would use NOT on boolean
> values, while you would use NULL to test for NIL. (To test an empty
> list, you would use ENDP, but it's not identical to NULL: ENDP signals
> an error if you give it a non-NIL atom).
>
> Here, we would consider that tip-rate is of type (or null real), not of
> type (or boolean real), since it wouldn't make any sense to assign true
> (T or some other true value) to tip-rate.
>

Makes sense, but this does not explain why my code works in the enter-bill-amount function, but not the enter-tip-rate function. I changed the code in both places, so now it reads:

(if (or (null tip-rate) (minusp tip-rate))

and still compiles with this message:

; in: DEFUN ENTER-TIP-RATE
; (MINUSP TIP-RATE)
; --> < IF <
; ==>
; TIP-RATE
;
; note: deleting unreachable code
;
; compilation unit finished
; printed 1 note

I am at a loss. Why would it work in function A but not function B?

> Hence the deleting unreachable code you have here (you tested twice
> tip-rate in your OR).

But isn't that what I am doing with the suggested revision?

> (or (parse-float (prompt-read "Enter bill amount"))
> nil)
>
> the OR is useless here. There is only one value that is false, this is
> NIL, so the only way to go to the alternative of this OR is for
> parse-float to return NIL, which is already the same value as what you
> have in the alternative. So it's useless.
>
> (parse-float (prompt-read "Enter bill amount"))
>
> would be just good.

Thank you. I have changed it.

> (format *query-io* "~a: " prompt)
>
> You may use ~& to ensure that a newline is inserted when needed:
>
> (format *query-io* "~&~a: " prompt)
>

I made these changes as well.

> (setf bill-amount (read-from-string (format nil "~$" bill-amount)))
>
> Very not a nice way to do it. You may use ROUND and adjust it back
> again:
>
> (let ((sub-devise 0.01D0))
> (setf bill-amount (* (round bill-amount sub-devise) sub-devise)))
>
>
>
> For more advanced Common Lisp tools to do something similar, have a look
> at RESTART-CASE (clsh restart-case has a nice example).
>
> In simple cases, you may also use CHECK-TYPE. Try:
>
> (let ((price -42.12))
> (check-type price (real 0 200))
> price)

Dually noted. When I get my logic fixed, I'll work on float precision. For now, this part of my code is awful, but it works well enough for now.

> (zerop (length d))
>
> Not a good idea to do that raw: people^W users are known to type
> whitespaces in empty fields, so at the very least, you want to do:
>
> (zerop (length (string-trim #(#\space #\tab) d)))

Thank you, I have changed it to the suggested version.

> (when (zerop (length d))
> (progn
> (setf tip-rate 15)
> (return)))
>
> PROGN is useless here, WHEN already have an implicit PROGN:
>
> (when (zerop (length d))
> (setf tip-rate 15)
> (return))

Removed useless code. Thank you.

> You could use the complex form of LOOP:
>
> (defun enter-tip-rate ()
> "Enter tip rate."
> (loop
> :with tip-rate := nil
> :while (or (null tip-rate) (minusp tip-rate))
> :do (let ((line (prompt-read "Enter the tip rate (without the %, default is 15)")))
> (when (zerop (length (string-trim #(#\space #\tab) line)))
> (setf tip-rate 15)
> (loop-finish))
> (setf tip-rate (parse-float line))
> (when tip-rate
> (loop-finish))
> (format t "That is not a valid amount! Digits only, please.~%"))
> :finally (return (/ tip-rate 100.0))))

Interestingly enough, this code produces the same warning in sbcl:

; in: DEFUN ENTER-TIP-RATE
; (MINUSP TIP-RATE)
; --> < IF <
; ==>
; TIP-RATE
;
; note: deleting unreachable code
;
; compilation unit finished
; printed 1 note

I am very confused and still stumped.

I have updated my paste-bin with all the code and the changes you suggested I make:
http://pastebin.co...

Pascal J. Bourguignon

12/26/2015 12:23:00 AM

0

Tim Hawes <trhawes@gmail.com> writes:

> On Friday, December 25, 2015 at 4:30:28 PM UTC-5, Lieven Marchand wrote:
>> (return) doesn't do what you think it does.
>
>
> I think it breaks out of loop. Does it do something else?

No, it's ok.

However, notice that this is because you used a simple loop.
In my message, I provided an example of a complex loop, and there the
semantics of return are not convenient (in this case), and I used
instead (loop-finish) which is only available in complex loops.

(loop (loop-finish)) is not valid;
(loop (return)) is valid;
(loop :do (loop-finish)) is valid.
^
|
+-- a loop keyword makes it a complex loop.
(check clhs 6.1.1.2 Loop Keywords).


--
__Pascal Bourguignon__ http://www.informat...
â??The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.� -- Carl Bass CEO Autodesk

Tim Hawes

12/26/2015 12:35:00 AM

0

informatimago, my first reply was directed to Lieven Marchand, who indicated that (return) does not do what I think it does. My second reply was directed toward you. I tried your loop, and I am getting the same compile warning. Baffling.....


On Friday, December 25, 2015 at 7:22:57 PM UTC-5, informatimago wrote:
> Tim Hawes writes:
>
> > On Friday, December 25, 2015 at 4:30:28 PM UTC-5, Lieven Marchand wrote:
> >> (return) doesn't do what you think it does.
> >
> >
> > I think it breaks out of loop. Does it do something else?
>
> No, it's ok.
>
> However, notice that this is because you used a simple loop.
> In my message, I provided an example of a complex loop, and there the
> semantics of return are not convenient (in this case), and I used
> instead (loop-finish) which is only available in complex loops.
>
> (loop (loop-finish)) is not valid;
> (loop (return)) is valid;
> (loop :do (loop-finish)) is valid.
> ^
> |
> +-- a loop keyword makes it a complex loop.
> (check clhs 6.1.1.2 Loop Keywords).
>
>
> --
> __Pascal Bourguignon__ http://www.informat...
> "The factory of the future will have only two employees, a man and a
> dog. The man will be there to feed the dog. The dog will be there to
> keep the man from touching the equipment." -- Carl Bass CEO Autodesk

Pascal J. Bourguignon

12/26/2015 12:45:00 AM

0

Tim Hawes <trhawes@gmail.com> writes:

>> You could use the complex form of LOOP:
>>
>> (defun enter-tip-rate ()
>> "Enter tip rate."
>> (loop
>> :with tip-rate := nil
>> :while (or (null tip-rate) (minusp tip-rate))
>> :do (let ((line (prompt-read "Enter the tip rate (without the %, default is 15)")))
>> (when (zerop (length (string-trim #(#\space #\tab) line)))
>> (setf tip-rate 15)
>> (loop-finish))
>> (setf tip-rate (parse-float line))
>> (when tip-rate
>> (loop-finish))
>> (format t "That is not a valid amount! Digits only, please.~%"))
>> :finally (return (/ tip-rate 100.0))))
>
> Interestingly enough, this code produces the same warning in sbcl:
>
> ; in: DEFUN ENTER-TIP-RATE
> ; (MINUSP TIP-RATE)
> ; --> < IF <
> ; ==>
> ; TIP-RATE
> ;
> ; note: deleting unreachable code
> ;
> ; compilation unit finished
> ; printed 1 note
>
> I am very confused and still stumped.
>
> I have updated my paste-bin with all the code and the changes you suggested I make:
> http://pastebin.co...

Ok. There's a bug in enter-tip-rate:

Since tip-rate is initialized to NIL, the compiler can generate code to
skip over the while test the first time. Then tip-rate is set to a
NIL or a real returned by PARSE-FLOAT. If it's not NIL, then (WHEN
tip-rate (LOOP-FINISH)) will finish the loop, so FORMAT is only called
when tip-rate is NIL, and then the :WHILE test is always true.

(when tip-rate (loop-finish))

should be:

(when (and tip-rate (minusp tip-rate)) (loop-finish))

and then we don't need :WHILE. So we can as well revert to a simple
loop:

(defun enter-tip-rate ()
"Enter tip rate."
(let ((tip-rate nil))
(loop
(let ((line (prompt-read "Enter the tip rate (without the %, default is 15)")))
(when (zerop (length (string-trim #(#\space #\tab) line)))
(setf tip-rate 15)
(return))
(setf tip-rate (parse-float line))
(when (and tip-rate (minusp tip-rate))
(return))
(format t "That is not a valid amount! Digits only, please.~%")))
(/ tip-rate 100.0)))

Sorry for the confusion.

--
__Pascal Bourguignon__ http://www.informat...
â??The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.� -- Carl Bass CEO Autodesk

Tim Hawes

12/26/2015 1:43:00 AM

0

On Friday, December 25, 2015 at 7:44:47 PM UTC-5, informatimago wrote:
> informatimago writes:
> Ok. There's a bug in enter-tip-rate:
>
> Since tip-rate is initialized to NIL, the compiler can generate code to
> skip over the while test the first time. Then tip-rate is set to a
> NIL or a real returned by PARSE-FLOAT. If it's not NIL, then (WHEN
> tip-rate (LOOP-FINISH)) will finish the loop, so FORMAT is only called
> when tip-rate is NIL, and then the :WHILE test is always true.
>
> (when tip-rate (loop-finish))
>
> should be:
>
> (when (and tip-rate (minusp tip-rate)) (loop-finish))
>
> and then we don't need :WHILE. So we can as well revert to a simple
> loop:
>
> (defun enter-tip-rate ()
> "Enter tip rate."
> (let ((tip-rate nil))
> (loop
> (let ((line (prompt-read "Enter the tip rate (without the %, default is 15)")))
> (when (zerop (length (string-trim #(#\space #\tab) line)))
> (setf tip-rate 15)
> (return))
> (setf tip-rate (parse-float line))
> (when (and tip-rate (minusp tip-rate))
> (return))
> (format t "That is not a valid amount! Digits only, please.~%")))
> (/ tip-rate 100.0)))
>
> Sorry for the confusion.

Bingo! Except that I wanting to prevent the use from entering a negative number, so I changed the when to an if, like so:

(if (and tip-rate (minusp tip-rate))
(format t "That is not a valid amount! Digits only, please.~%")
(return))
and now it works!

Although your explanation is clear, I am a lil' baffled on why the compiler was "over-achieving" on this. Looking for that ol' adage: the take away from this lesson that will help me remember not to code the problem the way I had. Any advice?

I guess for one, I did not need to put all my checks in up front at the top of the loop.

Anyways, even if you don't have anything to add, thank you so much for your help!

Pascal J. Bourguignon

12/26/2015 3:12:00 AM

0

Tim Hawes <trhawes@gmail.com> writes:

> On Friday, December 25, 2015 at 7:44:47 PM UTC-5, informatimago wrote:
>> informatimago writes:
>> Ok. There's a bug in enter-tip-rate:
>>
>> Since tip-rate is initialized to NIL, the compiler can generate code to
>> skip over the while test the first time. Then tip-rate is set to a
>> NIL or a real returned by PARSE-FLOAT. If it's not NIL, then (WHEN
>> tip-rate (LOOP-FINISH)) will finish the loop, so FORMAT is only called
>> when tip-rate is NIL, and then the :WHILE test is always true.
>>
>> (when tip-rate (loop-finish))
>>
>> should be:
>>
>> (when (and tip-rate (minusp tip-rate)) (loop-finish))
>>
>> and then we don't need :WHILE. So we can as well revert to a simple
>> loop:
>>
>> (defun enter-tip-rate ()
>> "Enter tip rate."
>> (let ((tip-rate nil))
>> (loop
>> (let ((line (prompt-read "Enter the tip rate (without the %, default is 15)")))
>> (when (zerop (length (string-trim #(#\space #\tab) line)))
>> (setf tip-rate 15)
>> (return))
>> (setf tip-rate (parse-float line))
>> (when (and tip-rate (minusp tip-rate))
>> (return))
>> (format t "That is not a valid amount! Digits only, please.~%")))
>> (/ tip-rate 100.0)))
>>
>> Sorry for the confusion.
>
> Bingo! Except that I wanting to prevent the use from entering a negative number, so I changed the when to an if, like so:
>
> (if (and tip-rate (minusp tip-rate))
> (format t "That is not a valid amount! Digits only, please.~%")
> (return))
> and now it works!
>
> Although your explanation is clear, I am a lil' baffled on why the
> compiler was "over-achieving" on this.

Well, it's the speciality of sbcl compared to the other implementations,
to be over-jealous with compilation-time type inference and checking,
and dead-code prunning. It tends to issue a lot of such warning (and I
don't find that they're always justified). In this case, it detected an
error, so it's not bad.


> Looking for that ol' adage: the
> take away from this lesson that will help me remember not to code the
> problem the way I had. Any advice?
>
> I guess for one, I did not need to put all my checks in up front at the top of the loop.

In "structured programming" a lot of stress is made on WHILE loops,
since they have very clear and definite entry point, stop condition, and
exit point (the only thing they lack formally is the initialization that
is put independently before the loop), which allows to make a nice
analysis or construction of the loop using Hoare logic
(pre-/post-conditions and loop invariants).

However, in practice, it's often the case that it is easier to write
loops with multiple exit points as you have (multiple returns or
loop-finish).



> Anyways, even if you don't have anything to add, thank you so much for your help!

--
__Pascal Bourguignon__ http://www.informat...
â??The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.� -- Carl Bass CEO Autodesk