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?

zeroclaim
  • 57
  • 2
  • 7

2 Answers2

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.

sds
  • 58,617
  • 29
  • 161
  • 278
  • 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 Jul 24 '22 at 03:55
  • @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)` – sds Jul 24 '22 at 03:59
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)

Martin Půda
  • 7,353
  • 2
  • 6
  • 13