Define interactive functions

Last week I wrote a simple function in Common Lisp, to calculate whether I should sold a national debt or not. The function takes 6 parameters, which I found a little difficult to use. But it's also not worth to add a command line arguments parsing library and write a formal *ui* for it. So inspired by Emacs interactive function, I wrote a very simple macro which defines functions that can be called interactively:

(defun prompt-ask (&rest args)
  (apply #'format *standard-output* args)
  (force-output)
  (read *standard-input*))
  
(defmacro define-interactive (name arg-specs &body body)
  (let ((arg-list (loop for (arg-name prompt)
                          in (mapcar #'(lambda (spec)
                                         (destructuring-bind (sym prompt)
                                             (if (atom spec)
                                                 (list spec (symbol-name spec))
                                                 spec)
                                           (list sym (format nil "~a: " prompt))))
                                     arg-specs)
                        collect `(,arg-name (prompt-ask ,prompt)))))
    `(defun ,name (&optional ,@arg-list)
       ,@body)))

With this macro, I defined my debt calculation function as:

(define-interactive suggest ((parvalue "票面总值")
                             (rate "利率(%)")
                             (rate-for-interest "每年兑息的再投资利率(%)")
                             (years "剩余期限中的年数")
                             (days "剩余期限中的天数")
                             (current-value "现值"))
  (let* ((rate (/ rate 100))
         (rate-for-interest (/ rate-for-interest 100))
         (years (+ years (/ days 365.0)))
         (year-interest (* parvalue rate))
         (total-expected (+ parvalue
                            (loop for i from (floor years) downto 0
                                  sum (* year-interest
                                         (expt (1+ rate-for-interest) i)))))
         (minimal-next-rate (if (>= current-value total-expected)
                                0
                                (loop for i from 0.0005 upto 0.5 by 0.001
                                      for money = (* current-value
                                                     (expt (1+ i) years))
                                      when (> money total-expected)
                                        do (return i)
                                      finally (return nil)))))

    (format t "持有到期预期收入: ~,2f~%现在卖出需要的下一个最少利率是: ~,2f%~%"
            total-expected
            (* 100 (or minimal-next-rate 100)))
    (values)))

Now we have a function 'suggest' which takes 6 optional parameters, and ask user input for the unspecified ones:

GZ-UTIL> (suggest)
票面总值: 150000
利率(%): _

You can still call it non-interactively by specifying all parameters.

The idea and code is so simple that may not worth a blog post, but I do feel happy when writing it. So here we have a blog for it anyway :)

---------------------

Published on 2024-12-25

The content for this site is licensed under:

CC-BY-SA