mutility

2021-08-07

modula's utilities

Upstream URL

github.com/defaultxr/mutility

Author

modula t.

License

MIT

README.org

mutility

modula's many-multi mega-miscellaneous meta-modulated macro-mutated utilities

A collection of various utility functions and macros I use in my libraries.

This is mostly for deduplication of code so I don't have to keep several copies of these functions across different projects. I don't really recommend depending on this library in your code since I may rename, rearrange, or remove things without warning. That being said, if you have any issues or need help with the library, please don't hesitate to let me know.

All exported symbols are documented via their docstrings, which are of course accessible via Lisp's standard documentation and describe functions.

1Noteworthy features

1.1Sugar

1.1.1a macro

Quickly generate lists containing repeated items or numeric series.

;; four 4s:
(a 1 4!4 2) ;=> (1 4 4 4 4 2)

;; 10 random numbers:
(a 'hello (random 10)!10 'goodbye) ;=> (HELLO 2 5 3 9 5 0 9 1 9 6 GOODBYE)

;; series from -2 to 2:
(a 'foo -2..2 'bar) ;=> (FOO -2 -1 0 1 2 BAR)

1.1.2fn macro

A sweeter way to write lambda. Underscore (_) is treated as the argument of the lambda.

(fn (random 10))
;; is the same as
(lambda () (random 10))

;; and
(fn (+ 2 _))
;; is the same as
(lambda (_) (+ 2 _))

1.1.3keys generic

Get a list of the keys of hash tables, plists, and anything else that defines a method for it. For example, cl-patterns defines a method for its event class.

(keys '(:foo 1 :bar 2)) ;=> (:FOO :BAR)

(keys (alexandria:plist-hash-table (list :this 1337 :that 69))) ;=> (:THIS :THAT)

1.2Strings

1.2.1concat

Concatenate its non-nil inputs together into a string, similar to the elisp function of the same name.

(defun say-hello (name &optional ask)
  (concat "Hello, " name "!" (when ask " How are you doing today?")))

(say-hello "Allison" t) ;=> "Hello, Allison! How are you doing today?"
(say-hello "Gordon" nil) ;=> "Hello, Gordon!"

1.2.2parse-boolean

Parse a string as a boolean by looking for common true/false inputs like y/n, 1/0, on/off, true/false, enable/disable, etc. If the input is not a known boolean-like string, defaults to the specified default value

(parse-boolean "1") ;=> t
(parse-boolean "0") ;=> nil
(parse-boolean "y") ;=> t
(parse-boolean "N") ;=> nil
(parse-boolean "blah" t) ;=> t ;; "blah" can't be interpreted as a boolean, so it defaults to the provided value of t.

1.2.3"friendly string" functions

A few functions to generate more human-readable strings from numeric values.

;; turn improper fractions into proper ones:
(friendly-ratio-string 13/4) ;=> "3 1/4"
(friendly-ratio-string 99/13) ;=> "7 8/13"

;; turn a number of seconds into the typical time notation:
(friendly-duration-string 67) ;=> "1:07"
;; 3600 seconds is one hour:
(friendly-duration-string 3600) ;=> "1:00:00"

1.3Math

1.3.1wrap

Wrap a number within a range like cl:mod but taking into account a lower bound as well.

1.3.2rounding functions

Round, floor, or ceiling to the nearest multiple of a given number with round-by, floor-by, and ceiling-by.
(round-by 0.39 0.2) ;=> 0.4
(round-by 97 25) ;=> 100

(floor-by 0.39 0.2) ;=> 0.2
(floor-by 97 25) ;=> 75

(ceiling-by 0.22 0.2) ;=> 0.4
(ceiling-by 27 25) ;=> 50

1.4Sequences

1.4.1length-upto

Count the number of items in a list, not counting beyond a specified number.

(length-upto (make-list 2000) 20) ;=> 20

1.4.2most

Get the most X item in a list, where X can be any comparison function. Similar to the standard reduce function, except that the key argument is only used for comparison, and the actual item from the list is still returned.

;; get the item with the smallest car:
(most '< '((2 :bar) (3 :baz) (1 :foo)) :key 'car) ;=> (1 :FOO)

;; compare this to `reduce', which returns the result of calling KEY on the item, instead of returning the item itself:
(reduce 'min '((2 :bar) (3 :baz) (1 :foo)) :key 'car) ;=> 1

1.4.3flatten-1

Like alexandria:flatten but only flattens one layer.

1.4.4subseq*

Like the standard subseq, but the START and END parameters can be negative to represent indexing from the end of the list.

(subseq* (list 0 1 2 3 4 5) -3) ;=> (3 4 5)
(subseq* (list 0 1 2 3 4 5) -3 -1) ;=> (3 4)

1.4.5left-trim

Like string-left-trim but for lists instead of strings.

1.5Randomness

1.5.1random-coin

1.5.2random-range

1.5.3exponential-random-range

1.5.4random-gauss

1.6Hash Tables

1.6.1save and restore

1.7Miscellaneous

1.7.1open-url

1.7.2generate-temporary-file-name

1.8Swank Extensions

1.9Ranges

Functionality for mapping numbers from one range to another.

1.10defgeneric*

1.11defclass*

1.12Looping

Looping functionality is in the "loopy" subsystem; run (ql:quickload :mutility/loopy) to load it.

1.12.1mapcar* and dolist*

Like the standard mapcar and dolist, but includes the current index into the list.

1.12.2while macro

Your standard "while" loop that repeats its body as long as its test condition is true. Additionally, it will return the last non-nil value it processed in the body or the test.

1.12.3do-while macro

Like while, but the body is run before the test condition is checked; i.e. the body is always run at least once.

1.12.4until macro

The opposite of while; runs its body as long as its test condition is false.

1.12.5accumulating macro

Efficiently append to a list, which is then returned.

(accumulating (dotimes (n 5) (accumulate (random 10)))) ;=> (0 2 3 4 1)

2Sub-systems

  • mutility/loopy is a small collection of various looping constructs like dolist*, while, do-while, etc.
  • mutility/generic-cl defines a few extensions to the generic-cl library.
  • mutility/test-helpers includes a few functions that are mostly useful for test suites.
  • mutility/tests is the FiveAM-based test suite for the library.

3Tour

All source files are in the src/ directory.

Mutility also includes a few extensions for other systems in src/extensions/:

The test suite is located in t/. To run the tests:

(asdf:test-system :mutility)

4Future

Ideas, and things that need to be done.
  • Come up with a better name for the a macro.
  • Remove/rename accumulate to prevent clashes with generic-cl's accumulate function.
  • Write functions to parse docstrings (i.e. to extract example code from them so they can be treated as tests).
  • Write more tests for everything.
  • Test docstring examples with the docstring-parsing function once it's written.
  • Write a test to check for symbol clashes against various other libraries: alexandria, serapeum, cl-patterns, thundersnow, etc.
  • Maybe split out stuff into subsystems? i.e. sugar, files, etc.
  • Allow fn to accept more than one argument.

Dependencies (5)

  • alexandria
  • closer-mop
  • fiveam
  • local-time
  • trivial-do

Dependents (2)

  • GitHub
  • Quicklisp
  • Sponsor