2

PRE-FACE

Avid libre computing and copyleft supporter,

Long year GNU Emacs mainstream user,

just surpassing the point where GNU Emacs becomes play dough,

essentially by starting to learn eLisp.

Before that turned into beginner to intermediate bash script coder—so, in bash I'm familiar with some basic concepts of programming, and now wondering how they look like in eLisp.

STARTING POINT

I have a function.

What it does in simple terms:

Transforming "4" into "4.0", leaving "742.0" as "742.0"—no matter if data type of input is NUMBER or STRING.

Definition:

;;    decimalizer and decimalizing-all-integers-no-matter-how-many                                                                                                                                                                                             
;;                                                                                                                                                                                                                                                             
;;    Copyleft 🄯 2024                                                                                                                                                                                                                                          
;;                                                                                                                                                                                                                                                             
;;    This program is free software: you can redistribute it and/or modify                                                                                                                                                                                     
;;    it under the terms of the GNU Affero General Public License as published by                                                                                                                                                                              
;;    the Free Software Foundation, either version 3 of the License, or                                                                                                                                                                                        
;;    (at your option) any later version.                                                                                                                                                                                                                      
;;                                                                                                                                                                                                                                                             
;;    This program is distributed in the hope that it will be useful,                                                                                                                                                                                          
;;    but WITHOUT ANY WARRANTY; without even the implied warranty of                                                                                                                                                                                           
;;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                                                                                                                                                                                             
;;    GNU Affero General Public License for more details.                                                                                                                                                                                                      
;;                                                                                                                                                                                                                                                             
;;    You should have received a copy of the GNU Affero General Public License                                                                                                                                                                                 
;;    along with this program. If not, see <https://www.gnu.org/licenses/>.                                                                                                                                                                                    

;; 2-part eLisp function                                                                                                                                                                                                                                       
;; Ensuring that arbitrary number of ARGS in form of NUMBER or numerical STRING                                                                                                                                                                                
;; are decimals, transforming if necessary.                                                                                                                                                                                                                    

; part 1: decimalizer                                                                                                                                                                                                                                          
(defun decimalizer (num-value)
  "Usage example:                                                                                                              
   Input: NUMBER 4 or 4.0 or STRING 4 or 4.0                                                                                   
   Output: NUMBER 4.0"
  (let ((x num-value))
    ; parse 'n process section                                                                                                                                                                                                                                 
    (cond ((numberp x) x)
      ((stringp x) (setq x (string-to-number x)))
      (t (message "Invalid input: Wrong data type - valid are numericals or values transformable into numerical data types."))
    )
    ; main section                                                                                                                                                                                                                                             
    (string-to-number
      (replace-regexp-in-string "[0-9]*$" "\\&.0"
                (number-to-string x)
      )
    )
  )
)

; part 2: decimalizing-all-integers-no-matter-how-many                                                                                                                                                                                                         
(defun decimalizing-all-integers-no-matter-how-many (a b c d e f g)
  (let ((a (decimalizer a))
    (b (decimalizer b))
    (c (decimalizer c))
    (d (decimalizer d))
    (e (decimalizer e))
    (f (decimalizer f))
    (g (decimalizer g)))
    (insert (number-to-string a))
    (newline)
    (insert (number-to-string b))
    (newline)
    (insert (number-to-string c))
    (newline)
    (insert (number-to-string d))
    (newline)
    (insert (number-to-string e))
    (newline)
    (insert (number-to-string f))
    (newline)
    (insert (number-to-string g))
    (newline)
  )
)

Usage example:

Input:

(decimalizing-all-integers-no-matter-how-many 1 2 4 "8.0" 2 "343" 9)

Output:

1.0
2.0
4.0
8.0
2.0
343.0
9.0

CODE EVALUATION of part 2 decimalizing-all-integers-no-matter-how-many

The variable handling is very repetitive. The code would be far more efficient and easier to understand, if the all the variables would be grouped together in processing. In bash, when you invoke a tool with additional parameters, all these additional parameters automatically are stored in this $ building block—a variable. While coding one can access the first additional parameter by variable"$1", the second by variable "$2", or all at once by variables "$@" or "$*". Building upon that one can insert just 1 single set of tool invocations, like "First take VARIABLE and make sure its a NUMBER, then transform data type to STRING, do regular expression string replacement, and finally transform data type back to NUMBER.", and let the row of variables loop through this one by one, instead of creating that code block x times with just having the variable spot changed from a (1st code block) to b (2nd code block), and so on, as is right now in eLisp edition of the program.

The number of arguments is fixed, not arbitrary. Number of variables passed to this tool is supposed to don't matter; no matter if

(decimalizing-all-integers-no-matter-how-many 1 2 4 "8.0" 2 "343" 9)

or

(decimalizing-all-integers-no-matter-how-many 1 2 4 "8.0" 9)

or

(decimalizing-all-integers-no-matter-how-many 1 2 4 "8.0" 2 "343" 9 324324 8 8498)

always all passed variables should be processed as intended

1.0
2.0
4.0
8.0
2.0
343.0
9.0

or

1.0
2.0
4.0
8.0
9.0

or

1.0
2.0
4.0
8.0
2.0
343.0
9.0
324324.0
8.0
8498.0

In bash, that flexible way of handling the number of passed arguments is pretty much built-in, default.

WHAT I'VE TRIED

—all in vain due to lack of skills, so it could be the right direction.

• function dolist

&rest

• function mapcar

lambda

• Researching lots of associated documentation and code examples

CHALLENGE

In given situation, how to simplify code correctly by grouping all arguments together to a list, and how to loop through that list, so that desired output remains?

In given situation, how to implement argument section, so that it's flexible—instead of requiring a fixed number of arguments as input—so that desired output remains?

7
  • 1
    emacs.stackexchange.com/tags/elisp/info - please don't use the elisp tag except in the specific situation described in the link.
    – NickD
    Commented Jun 18 at 15:02
  • 1
    You should read an introductory book on Lisp. The "Introduction to Programming in Emacs Lisp" is available in your Emacs: just say C-h i g(eintr) and start reading.
    – NickD
    Commented Jun 18 at 18:21
  • 1
    The intro is excellent. You should then follow that up by frequently referring to the detailed Emacs Lisp manual (C-h R elisp). There you will find that the number stack in Elisp makes no distinction between 4.0 and 4. That's not to say that there aren't different floating-point and integral number types: there are. It's just that type promotion, demotion and coercion happen transparently. Once it's a number it's a number. In other words, there is no point in massaging a string representing "4" into one representing "4.0" prior to conversion using string-to-number. Commented Jun 18 at 19:11
  • 1
    Then you could have a look at the format function. It's basically a port of the C printf function to Elisp. I think that might be sufficient to replace your current replace-regexp-in-string call. Commented Jun 18 at 19:13
  • 1
    The one exception to dynamic type promotion in Elisp is integer division: (let ((x 4)) (/ 6 x)) => 1 whereas (let ((x 4)) (/ 6 (float x))) => 1.5. This is because integer-only division (called div in some languages) is sometimes what you want. Commented Jun 18 at 19:22

2 Answers 2

6

You can use &rest to get all arguments in a list and then use dolist to loop over each element of the list individually e.g.:

    (defun decimalizing-all-integers-no-matter-how-many (&rest args)
      (dolist (arg args)
        (insert (number-to-string (decimalizer arg)))
        (newline)))

Refs:

  1. dolist
  2. Argument lists

Two additional points:

  1. You can use a mapping function instead of an explicit loop (as you guessed). Something like
(defun decimalizing-all-integers-no-matter-how-many (&rest args)
  (mapc (lambda (arg) (insert (number-to-string (decimalizer arg)))) args))

Mapping functions like mapcar and mapc loop implicitly over the elements of the list that is their second argument and apply to each element the function that is specified as the first argument. mapcar accumulates the results in a list which it returns; mapc is used for side-effects only (here, inserting a string into the buffer) and does not return any useful value.

  • The decimalizer function should probably return a string instead of a number: that would avoid the number-to-string call in the parent function. I would write it as follows:
(defun decimalizer (x)
  "Return a string representation of the argument (which must be a number
or a string representation of a number) with one digit after the decimal point."
  (let ((y (cond
              ((stringp x) (string-to-number x))
              ((numberp x) x)
              (t (user-error "Argument must be number or string: %S" x)))))
    (format "%.1f" y)))

so we convert whatever we get into a number (if possible), convert that to a string using format and return the string. This would round the number to the nearest 10th which may or may not be what you want. But it is simpler than converting to a string and then doing regexp matching, which is generally fraught with dangers in numerical computing and should be avoided.

0

You don't need either the let or the setq and you can avoid the round-trip conversion from string to number and back to string.

(defun decimalizer (num-value)
  "Usage example:                                                                                                              
   Input: NUMBER 4 or 4.0 or STRING 4 or 4.0                                                                                   
   Output: NUMBER 4.0"
  (string-to-number
    (replace-regexp-in-string "[0-9]*$" "\\&.0"
              (funcall
                (cond 
                  ((numberp x) #'number-to-string)
                  ((stringp x) #'identity) ; no modification
                  (t (user-error "Invalid input: Wrong ...")
                num-value)))

What the cond form does is select which function to apply to the given value; identity means "just return the argument completely unmodified". user-error terminates abruptly so you never get to the funcall invocation.

2
  • 1
    IMO, doing regex manipulations to change a number (OK, the string representation of a number) is not advisable. See my edit to the other answer.
    – NickD
    Commented Jun 19 at 1:52
  • 1
    Agreed. As I said in my comment above yesterday, format is the way to go. Commented Jun 20 at 7:30

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.