0

I am learning Lisp and, just for practice/education, am trying to define a function that will

  1. ask the user to enter a number until they enter an integer > 0 [copied from Paul Graham's Ansi Common Lisp]

  2. print that number and subtract 1 from it, repeat until the number hits 0, then return.

I am trying to do this via passing 2 functions into a higher-order function - one to get the number from the user, and another recursive [just for fun] function that prints the number while counting it down to 0.

Right now my higher-order function is not working correctly [I've tested the first 2 and they work fine] and I cannot figure out why. I am using SBCL in SLIME. My code for the 3 functions looks like this:

(defun ask-number ()
  (format t "Please enter a number. ")
  (let ((val (read))) ; so val is a single-item list containing the symbol 'read'?
    (cond             ; no here read is a function call
      ((numberp val)
       (cond
     ((< val 0) (ask-number))
     (T val)))
      (t (ask-number))))))

(defun count-down (n)
  (cond
    ((eql n 0) n)
    (t
     (progn
       (format t "Number is: ~A ~%" n)
       (let ((n (- n 1)))
       (count-down n))))))

(defun landslide (f1 f2)
  (let (x (f1))
    (progn
      (format t "x is: ~A ~%" x)
      (f2 x)))))

but calling slime-eval-defun in landslide yields:

; SLIME 2.27; in: DEFUN LANDSLIDE
;     (F1)
; 
; caught STYLE-WARNING:
;   The variable F1 is defined but never used.

;     (SB-INT:NAMED-LAMBDA LANDSLIDE
;         (F1 F2)
;       (BLOCK LANDSLIDE
;         (LET (X (F1))
;           (PROGN (FORMAT T "x is: ~A ~%" X) (F2 X)))))
; 
; caught STYLE-WARNING:
;   The variable F1 is defined but never used.
; 
; caught STYLE-WARNING:
;   The variable F2 is defined but never used.
; in: DEFUN LANDSLIDE
;     (F2 X)
; 
; caught STYLE-WARNING:
;   undefined function: COMMON-LISP-USER::F2
; 
; compilation unit finished
;   Undefined function:
;     F2
;   caught 4 STYLE-WARNING conditions

I have tried several [what I consider] obvious modifications to the code, and they all fail with different warnings. Calling the function like (landslide (ask-number) (count-down)), ask-number prompts for user input as expected, but then SLIME fails with

invalid number of arguments: 0
   [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR]

I know I have to be missing something really obvious; can someone tell me what it is?

2 Answers 2

1

First: You are missing a set of parens in your let:

You have (let (x (f1)) ...) which binds 2 variables x and f1 to nil.

What you want is (let ((x (f1))) ...) which binds 1 variable x to the values of function call (f1)

Second: Common Lisp is a "lisp-2", so to call f2 you need to use funcall: (funcall f2 ...).

Finally: all your progns are unnecessary, and your code is hard to read because of broken indentation, you can use Emacs to fix it.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks - I made all the edits, but I still get the above error when calling (landslide (ask-number) (count-down)) - the one that says I'm passing count-down 0 arguments.
@zeroclaim: you are passing to landslide the result of a call to (count-down) which indeed has no arguments. what you want is (landslide #'ask-number #'count-down)
1

Before I reach an error in landslide, there are some notes about this code:

Your first function is hard to read- not just because of indentation, but because of nested cond.

  • You should always think about how to simplify condition branches- using and, or, and if you have only two branches of code, use if instead.
  • There are predicates plusp and minusp.
  • Also, don't forget to flush.

I'd rewrite this as:

(defun ask-number ()
  (format t "Please enter a number. ")
  (finish-output)
  (let ((val (read)))
    (if (and (numberp val)
             (plusp val))
        val
      (ask-number))))

Second function, count-down.

  • (eql n 0) is zerop
  • cond here has only two branches, if can be better
  • cond has implicit progn, so don't use progn inside cond
  • let is unnecessary here, you can use 1- directly when you call count-down

Suggested edit:

(defun count-down (n)
  (if (zerop n) n
    (progn 
      (format t "Number is: ~A ~%" n)
      (count-down (1- n)))))

Also, this function can be rewritten using loop and downto keyword, something like:

(defun count-down (n)
  (loop for i from n downto 0
        do (format t "Number is: ~A ~%" i)))

And finally, landslide. You have badly formed let here and as Common Lisp is Lisp-2, you have to use funcall. Note that let has also implicit progn, so you can remove your progn:

(defun landslide (f1 f2)
  (let ((x (funcall f1)))
    (format t "x is: ~A ~%" x)
    (finish-output)
    (funcall f2 x)))

Then you call it like this: (landslide #'ask-number #'count-down)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.