Search code examples
common-lispclisp

Read the proper input to a variable


I would like to have a variable containing an integer, that came from an input of a user. It can't accept strings neither decimal numbers.

I would like some help to understand what I am doing wrong here.

My code until now:

I appreciate the help.

(format t "~%Enter a number: ")
(loop (defvar numb (read))
(cond (((rationalp numb)1)
        (print "No decimal numbers are allowed, please enter an integer"))
      (((stringp numb)1)
        (print "No strings are allowed, please enter an integer"))
)
(when ((integerp numb)1) (return numb))
)

Solution

  • Working code

    Here is how I would do it:

    (defun ask-and-read (prompt)
      "Prompt the user and read his input."
      (princ prompt *query-io*)
      (force-output *query-io*)  ; flush the buffers
      (let ((*read-eval* nil))   ; close the security hole
        (read *query-io*)))
    
    (defun request-object (prompt predicate)
      "Ask the user for an object using prompt.
    Only accept data which satisfies the predicate."
      (loop
        for object = (ask-and-read prompt)
        when (funcall predicate object)
        return object
        do (format *query-io* "Alas, ~S (~S) does not satisfy ~S, please try again~%"
                   object (type-of object) predicate)))
    

    Example:

    > (request-object "Enter an integer: " #'integerp)
    Enter an integer: 4.6
    Alas, 4.6 (SINGLE-FLOAT) does not satisfy #<SYSTEM-FUNCTION INTEGERP>, please try again
    Enter an integer: 5/7
    Alas, 5/7 (RATIO) does not satisfy #<SYSTEM-FUNCTION INTEGERP>, please try again
    Enter an integer: asdf
    Alas, ASDF (SYMBOL) does not satisfy #<SYSTEM-FUNCTION INTEGERP>, please try again
    Enter an integer: 7
    ==> 7
    > (request-object "Enter a real: " #'realp)
    Enter a real: 4.5
    ==> 4.5
    > (request-object "Enter a real: " #'realp)
    Enter a real: 5/8
    ==> 5/8
    > (request-object "Enter a real: " #'realp)
    Enter a real: "sdf"
    Alas, "sdf" ((SIMPLE-BASE-STRING 3)) does not satisfy #<SYSTEM-FUNCTION REALP>, please try again
    Enter a real: 8
    ==> 8
    

    Please see the documentation for the facilities I used:

    Your mistakes

    Code formatting

    Your code is unreadable because you have incorrect indentation. Lispers do not count parens - this is the job for compilers and editors. We look at indentation. Please do yourself a favor and use Emacs - it will indent the code for you and you will often see your errors yourself.

    Defvar is a top-level form

    First of all, defvar is a top-level form which is used to define global variables, not set them. Subsequent calls do not change the value:

    (defvar *abc* 1)
    *abc*
    ==> 1
    (defvar *abc* 10)
    *abc*
    ==> 1   ; not 10!
    

    Use setq to set variable.

    Prefer local variables to global variables

    While Lisp does allow global variables, the predominant programming style in Lisp is the functional style: every function receives its "input" data as arguments and returns its "output" data as values. To achieve functional style, prefer a local to a global variable. You create local variables through let or let* or, in loop, see Local Variable Initializations.

    Cond and When have very specific syntax

    You have extra parens and 1(?!) in your cond and when forms.

    Remember, parens are meaningful in Lisp.

    Security first!

    Binding *read-eval* to nil before read is necessary to avoid a nuclear war if a user enters #.(launch-nuclear-missiles) in response to your prompt, because normally read evaluates whatever comes after #..