The Practical Common Lisp series focuses on small functions that come in handy when using Common Lisp, hoping to make a small contribution to the revival of Common Lisp. MAKE COMMON LISP GREAT AGAIN.

The preface

After writing Python for a while, it looks a lot like Common Lisp (CL). For example, both Python and CL support a variable number of function arguments. Write in Python

def foo(* args) :
    print(args)
Copy the code

In CL, it’s written as

(defun foo (&rest args)
  (print args))
Copy the code

Python’s syntax is more compact, while CL’s syntax is clearer. In addition, they all support keyword arguments. Written in Python

def bar(*, a=None, b=None) :
    print('a={}\tb={}'.format(a, b))
Copy the code

In CL, it’s PI

(defun bar (&key (a nil) (b nil(a))format t "a=~A~8Tb=~A~%" a b))
Copy the code

Although CL’s &key is still cleaner, the syntax for declaring parameter defaults is definitely better than Python’s.

Observant readers may have noticed that in Python there is a method called format (which belongs to the string class) and in CL there is a function called format. And, from the example above, they are both responsible for generating formatted strings, so are they similar?

The answer is no, THE FORMAT of CL is simply a mudslide in the field of formatting printing.

formatBasic usage of

Start with the example code above to introduce you to the basic use of format (hereinafter referred to as format without ambiguity) in CL. First, it takes at least two arguments:

  • The first parameter is controlledformatWhere the formatted string will be printed.tIndicates printing to standard output;
  • The second parameter is the hero of this article and is called control-string. It guidesformatHow to format.

It sounds mysterious, but it’s not that different from C’s fprintf.

In control strings, there are usually many directives that act like placeholders. Just as Python’s format method uses a variety of format_specs to format data of the corresponding type, there are also a variety of commands to control strings. Common ones include:

  • Printing binary digits~B, e.g.(format t "~B" 5)It prints 101;
  • Printing octal digits~O, e.g.(format t "~O" 8)It prints 10;
  • Printing decimal digits~D;
  • Printing hexadecimal numbers~X, e.g.(format t "~X" 161)It prints A1;
  • Print any type of~A, usually used when printing strings.

In addition, the format command also supports arguments. In Python, you can print the number 5 in binary form with a right-aligned, left-padded zero

print('{:0>8b}'.format(5))
Copy the code

The format function can do the same thing

(format t "~8,'0B" 5)
Copy the code

At this point, you might think that Format’s control strings are nothing more than a reworking of format’s methods with curly braces removed, colons replaced with wavy lines, and different syntax for arguments.

Next, let’s get into format’s dark tech territory.

formatAdvanced usage of

Hexadecimal conversion

Commands to print two, eight, ten, and hexadecimal have been listed above, but format supports other bases as well. Using the ~R command as an argument, the format command can print all numbers in base 2 to 36.

(format t "~3R~%" 36)   ; Printing the number 36 in base 3 gives 1100
(format t "~5R~%" 36)   ; Printing the number 36 in base 5 gives 121
(format t "~7R~%" 36)   ; Printing the number 36 in base 7 gives 51
(format t "~11R~%" 36)  ; Printing the number 36 in base 11 gives 33
(format t "~13R~%" 36)  ; Printing the number 36 in base 13 yields 2A
(format t "~17R~%" 36)  ; Printing the number 36 in base 17 produces 22
(format t "~19R~%" 36)  ; The number 36 is printed in base 19, resulting in 1H
(format t "~23R~%" 36)  ; Printing the number 36 in base 23 yields 1D
(format t "~29R~%" 36)  ; Printing the number 36 in base 29 yields 17
(format t "~31R~%" 36)  ; Printing the number 36 in base 31 gives 15
Copy the code

The maximum number is 36 because ten Arabic numerals plus twenty-six English letters make thirty-six. Will base 0 be used if no arguments are added to ~R? No, format prints numbers into English words

(format t "~R~%" 123) ; Print one hundred twenty-three
Copy the code

You can even tell format to print Roman numerals by adding the @ modifier

(format t "~@R~%" 123) ; Print out the CXXIII
Copy the code

God knows why such an unpopular feature would be built in.

Case conversion

You, as a careful reader, may have noticed that format ~X can only print uppercase letters, whereas in Python’s format method {: X} can print lowercase hexadecimal numbers. Even if you use ~x in the format function, this is invalid because the commands are case insensitive.

So how do you print lowercase hexadecimal numbers? The answer is to use the new command ~(and its companion command ~)

(format t "~(~X~)~%" 26) ; Print 1 a
Copy the code

Coordination: with the @ modifier, a total of four case styles can be implemented

(format t "~(hello world~)~%")   ; Prints hello world
(format t "~:(hello world~)~%")  ; Prints Hello World
(format t "~@(hello world~)~%")  ; Prints Hello world
(format t "~:@(hello world~)~%") ; Prints HELLO WORLD
Copy the code

Alignment control

In Python’s format method, you can control the width of what you print, as demonstrated in “Basic Uses of Format.” You can also control whether to align it left, right, or center if the minimum width set (in the above example, 8) exceeds the width occupied by the printed content (in the above example, 3).

In CL’s format function, there is no option to control the alignment of either ~B, ~D, ~O, or ~X; numbers are always right-aligned. To control alignment, use ~< and its companion ~>. For example, the following CL code makes a number left-aligned in eight widths

(format t "|~8<~B~; ~ > |" 5)
Copy the code

Print the contents of 101 | |. Unlike the other commands mentioned earlier, ~< does not consume the parameters after the control string. It only controls the layout of the string between ~< and ~>. This means that it works even if there is a string constant between ~< and ~>.

(format t "| ~ 8,,, '- < ~; hello~>|" 5)
Copy the code

The above code will print out after running | – hello | : 8 a minimum width of used for printing; Three commas (,) are empty, indicating that the second and third arguments of ~< are ignored. The fourth argument controls the characters used to fill the printed result, and since – is not a number, it is prefixed with single quotes; ~; Is the internal delimiter, and because of it, Hello is the right-most string, so it will be right-aligned.

If the content between ~< and ~> is ~; Split into three parts, you can also align left, center, and right

(format t "|~24
      
       |"
      ~;>) ; Print out the | left middle right |
Copy the code

jump

Typically, commands in control strings consume arguments, such as ~B and ~D commands. There are also commands like ~< that take no arguments. But there are even commands that can do more than one thing: ~*. For example, a colon modifier to ~* can cause the last argument to be consumed again

(format t "~8D~:*~8D~8D~%" 1 2) ; Print out 1, 1, 2
Copy the code

After ~8D consumes argument 1, ~:* redirects the next consumed argument to 1, so the second ~8D still gets argument 1 and the last one gets argument 2. Even though the control string appears to have three ~D commands and only two arguments, it still prints.

A good example in the format documentation is to pair ~* with ~P. ~P can print the letter s or nothing, depending on whether its argument is greater than 1. With ~:*, you can print the singular or plural form of the word according to the parameter

(format t "~D dog~:*~P~%" 1) ; Print 1 dog
(format t "~D dog~:*~P~%" 2) ; Print 2 dogs
Copy the code

You can even combine what you’ve learned over a lifetime

(format t "~@(~R dog~:*~P~)~%" 2) ; Print Two dogs
Copy the code

Conditions for printing

The ~[and ~] commands also come in pairs and are used for selective printing, but more like fetching a subscript element of an array than if in a programming language

(format t "~[~;one~;two~;three~]~%" 1) ; Print one,
(format t "~[~;one~;two~;three~]~%" 2) ; Print two
(format t "~[~;one~;two~;three~]~%" 3) ; Print three
Copy the code

But that’s a pretty weak feature. When you think about it, you don’t pass in a number as a subscript for no reason, and the number as a subscript is probably itself computed by a function like position, which requires passing in the item to be found and the entire list sequence, In order to use ~[, you have to hardcode each element in the list into a control string, which is quite the opposite.

It is useful to add the colon modifier, such as printing true (all objects except NIL) and false (NIL) in CL as the words true and false

(format t "~:[false~;true~]" nil) ; Print the false
Copy the code

Loop to print

If you’ve got parentheses and square brackets, you can’t do without curly brackets. Yes, ~{is also a command that iterates through a list. For example, if you want to print out each element in a list, separated by commas and Spaces, you can use the following code

(format t "~{~D~^, ~}" '(1 2 3)) ; Print out 1, 2, 3
Copy the code

There can also be more than one command between ~{and ~}, as in the following code, which consumes two elements of the list at a time

(format t "{~{\"~A\": ~D~^, ~}}" '(:a 3 :b 2 :c 1))
Copy the code

The printed result is {“A”: 3, “B”: 2, “C”: 1}. If you split the two format expressions into looping equivalents that do not use format, they would look something like this

; Is equivalent to (format t "~{~D~^, ~}" '(1 2 3))
(progn
  (do ((lst '(1 2 3) (cdr lst)))
      ((null lst))
    (let ((e (car lst)))
      (princ e)
      (when (cdr lst)
        (princ ","))))
  (princ #\Newline))

; With (the format t "{~ {\" ~ A \ ": ~ ~ ^ D, ~}}" '(: A 3:2 b: c, 1)) is equivalent
(progn
  (princ "{")
  (do ((lst '(:c 3 :b 2 :a 1) (cddr lst)))
      ((null lst))
    (let ((key (car lst))
          (val (cadr lst)))
      (princ "\" ")
      (princ key)
      (princ "\",")
      (princ val)
      (when (cddr lst)
        (princ ","))))
  (princ "}")
  (princ #\Newline))
Copy the code

In this case, ~{does allow users to write more compact code.

Parameterized parameter

In the previous example, although it is possible to print a number in different bases using ~R with different arguments, this argument is hardwired into the control string and is very limited. For example, if I wanted to define a function print-x-in-base-y so that the argument x could be printed as a y process, I might write it this way

(defun print-x-in-base-y (x y)
  (let ((control-string (format nil "~~~DR" y)))
    (format t control-string x)))
Copy the code

The flexibility of format, however, allows the user to place the command’s prefix arguments in the list after the control string, so a more concise implementation can be written as follows

(defun print-x-in-base-y (x y)
  (format t "~VR" y x))
Copy the code

And there’s more than one, you can write all the parameters as parameters

(defun print-x-in-base-y (x
                          &optional y
                          &rest args
                          &key mincol padchar commachar commainterval)
  (declare (ignorable args))
  (format t "~V,V,V,V,VR"
          y mincol padchar commachar commainterval x))
Copy the code

Congratulations on reinventing ~R and not supporting the: and @ modifiers.

Custom commands

It is quite troublesome to print the date and time string in CL, such as 2021-01-29 22:43

(multiple-value-bind (sec min hour date mon year)
    (decode-universal-time (get-universal-time(a))declare (ignorable sec))
  (format t "~4D-~2,'0D-~2,'0D ~2,'0D:~2,'0D~%"
          year mon date hour min))
Copy the code

CL doesn’t have built-in features like Python’s Datetime module. However, with format’s ~/ command, we can deeply customize the printed content by writing custom functions to be called in the control string. For example, to print the date and time in the above format, first define a custom function to use later

(defun yyyy-mm-dd-HH-MM (dest arg is-colon-p is-at-p &rest args)
  (declare (ignorable args is-at-p is-colon-p))
  (multiple-value-bind (sec min hour date mon year)
      (decode-universal-time arg)
    (declare (ignorable sec))
    (format dest "~4D-~2,'0D-~2,'0D ~2,'0D:~2,'0D~%"
            year mon date hour min)))
Copy the code

You can then use its name directly in the control string

(format t "~/yyyy-mm-dd-HH-MM/" (get-universal-time))
Copy the code

When running on my machine, print 2021-01-29 22:51.

Afterword.

Format can do a lot of things, CL HyperSpec has a detailed description of the format function, CL enthusiasts must not miss.

Finally, Python isn’t really that much like CL. As a fan of Common Lisp, I always look with envy at the clever use of Python’s __eq__, __ge__, and __len__ methods. Even though CL is called an extensible programming language, these mundane functions are still not easily available.

Read the original