about summary refs log blame commit diff
path: root/emacs/.emacs.d/wpc/list.el
blob: 83c4c5ccdc4256b02d9bbf42d6b057816cfd43b3 (plain) (tree)


















































                                                                                


                                                                           







































































                                                                                





















                                                                                




















































                                                                                




                                                                         













                                                 



                                                                                












                                                                  


                     
;;; list.el --- Functions for working with lists. -*- lexical-binding: t -*-
;; Author: William Carroll <wpcarro@gmail.com>

;;; Commentary:
;; Since I prefer having the `list/' namespace, I wrote this module to wrap many
;; of the functions that are defined in the the global namespace in ELisp.  I
;; sometimes forget the names of these functions, so it's nice for them to be
;; organized like this.
;;
;; Motivation:
;; Here are some examples of function names that I cannot tolerate:
;; - `car': Return the first element (i.e. "head") of a linked list
;; - `cdr': Return the tail of a linked list

;; As are most APIs for standard libraries that I write, this is heavily
;; influenced by Elixir's standard library.
;;
;; Elixir's List library:
;; - ++/2
;; - --/2
;; - hd/1
;; - tl/1
;; - in/2
;; - length/1
;;
;; Similar libraries:
;; - dash.el: Functional library that mimmicks Clojure.  It is consumed herein.
;; - list-utils.el: Utility library that covers things that dash.el may not
;;   cover.
;;   stream.el: Elisp implementation of streams, "implemented as delayed
;;   evaluation of cons cells."

;; TODO: Consider naming this file linked-list.el.

;; TODO: Support module-like macro that auto-namespaces functions.

;; TODO: Consider wrapping most data structures like linked-lists,
;; associative-lists, etc in a `cl-defstruct', so that the dispatching by type
;; can be nominal instead of duck-typing.  I'm not sure if this is a good idea
;; or not.  If I do this, I should provide isomorphisms to map between idiomatic
;; ways of working with Elisp data structures and my wrapped variants.

;; TODO: Are function aliases/synonyms even a good idea?  Or do they just
;; bloat the API unnecessarily?

;;; Code:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Dependencies
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; TODO: Move `prelude/assert' elsewhere so that I can require it without
;; introducing the circular dependency of list.el -> prelude.el -> list.el.
;;(require 'prelude)
(require 'dash)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defconst list/tests? t
  "When t, run the test suite.")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Library
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun list/new ()
  "Return a new, empty list."
  '())

(defun list/concat (&rest lists)
  "Joins `LISTS' into on list."
  (apply #'-concat lists))

(defun list/join (joint xs)
  "Join a list of strings, XS, with JOINT."
  (if (list/empty? xs)
      ""
    (list/reduce (list/first xs)
                 (lambda (x acc)
                   (string/concat acc joint x))
                 (list/tail xs))))

(defun list/length (xs)
  "Return the number of elements in `XS'."
  (length xs))

(defun list/get (i xs)
  "Return the value in `XS' at `I', or nil."
  (nth i xs))

(defun list/head (xs)
  "Return the head of `XS'."
  (car xs))

;; TODO: Learn how to write proper function aliases.
(defun list/first (xs)
  "Alias for `list/head' for `XS'."
  (list/head xs))

(defun list/tail (xs)
  "Return the tail of `XS'."
  (cdr xs))

(defun list/reverse (xs)
  "Reverses `XS'."
  (reverse xs))

(defun list/cons (x xs)
  "Add `X' to the head of `XS'."
  (cons x xs))

;; map, filter, reduce

;; TODO: Create function adapters like swap.
;; (defun adapter/swap (f)
;;   "Return a new function that wraps `F' and swaps the arguments."
;;   (lambda (a b)
;;     (funcall f b a)))

;; TODO: Make this function work.
(defun list/reduce (acc f xs)
  "Return over `XS' calling `F' on an element in `XS'and `ACC'."
  (-reduce-from (lambda (acc x) (funcall f x acc)) acc xs))

;; TODO: Support this. It seems like `alist/set' is not working as I expected it
;; to. Perhaps we should add some tests to confirm the expected behavior.
;; (cl-defun list/index (f xs &key (transform (lambda (x) x)))
;;   "Return a mapping of F applied to each x in XS to TRANSFORM applied to x.
;; The TRANSFORM function defaults to the identity function."
;;   (->> xs
;;        (list/reduce (alist/new)
;;                     (lambda (x acc)
;;                       (let ((k (funcall f x))
;;                             (v (funcall transform x)))
;;                         (if (alist/has-key? k acc)
;;                             (setf (alist-get k acc) (list v))
;;                           (setf (alist-get k acc) (list v))))))))
;; (prelude/assert
;;  (equal '(("John" . ("Cleese" "Malkovich"))
;;           ("Thomas" . ("Aquinas")))
;;         (list/index (lambda (x) (plist-get x :first-name))
;;                     '((:first-name "John" :last-name "Cleese")
;;                       (:first-name "John" :last-name "Malkovich")
;;                       (:first-name "Thomas" :last-name "Aquinas"))
;;                     :transform (lambda (x) (plist-get x :last-name)))))

(defun list/map (f xs)
  "Call `F' on each element of `XS'."
  (-map f xs))

(defun list/map-indexed (f xs)
  "Call `F' on each element of `XS' along with its index."
  (-map-indexed (lambda (i x) (funcall f x i)) xs))

(defun list/filter (p xs)
  "Return a subset of XS where predicate P returned t."
  (list/reverse
   (list/reduce
    '()
    (lambda (x acc)
      (if (funcall p x)
          (list/cons x acc)
        acc))
    xs)))

(defun list/reject (p xs)
  "Return a subset of XS where predicate of P return nil."
  (list/filter (lambda (x) (not (funcall p x))) xs))

(defun list/find (p xs)
  "Return the first x in XS that passes P or nil."
  (-find p xs))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Predicates
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun list/instance? (xs)
  "Return t if `XS' is a list.
Be leery of using this with things like alists.  Many data structures in Elisp
  are implemented using linked lists."
  (listp xs))

(defun list/empty? (xs)
  "Return t if XS are empty."
  (= 0 (list/length xs)))

(defun list/all? (p xs)
  "Return t if all `XS' pass the predicate, `P'."
  (-all? p xs))

(defun list/any? (p xs)
  "Return t if any `XS' pass the predicate, `P'."
  (-any? p xs))

(defun list/contains? (x xs)
  "Return t if X is in XS using `equal'."
  (-contains? xs x))

(defun list/xs-distinct-by? (f xs)
  "Return t if all elements in XS are distinct after applying F to each."
  (= (length xs)
     (->> xs (-map f) set/from-list set/count)))

;; TODO: Support dedupe.
;; TODO: Should we call this unique? Or distinct?

;; TODO: Add tests.
(defun list/dedupe-adjacent (xs)
  "Return XS without adjacent duplicates."
  (prelude/assert (not (list/empty? xs)))
  (list/reduce (list (list/first xs))
    (lambda (x acc)
      (if (equal x (list/first acc))
          acc
        (list/cons x acc)))
    xs))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Tests
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; (when list/tests?
;;   (prelude/assert
;;    (= 0
;;       (list/length '())))
;;   (prelude/assert
;;    (= 5
;;       (list/length '(1 2 3 4 5))))
;;   (prelude/assert
;;    (= 16
;;       (list/reduce 1 (lambda (x acc) (+ x acc)) '(1 2 3 4 5))))
;;   (prelude/assert
;;    (equal '(2 4 6 8 10)
;;           (list/map (lambda (x) (* x 2)) '(1 2 3 4 5)))))

(provide 'list)
;;; list.el ends here