Writing Macros in Lisp
One should not write Lisp macros arbitrarily (at least, I don’t think so).
I first pretend that I’m just doing symbolic manipulation of static lists.
(defun rpgtk-roll-mods (dice-pool &rest forms)
`(thread-last dice-pool
,(seq-map 'rpgtk--roll-mod-dispatcher forms)))
Dispatcher:
(defun rpgtk--roll-mod-dispatcher (form)
"Where FORM is a list"
(if (seq-contains-p form 'die)
(cl-case (car form)
(filter `(seq-filter (lambda (die) ,(cdr form))))
(reducer `(seq-reduce (lambda (acc die) ,(cdr form))))
(adjuster `(seq-map (lambda (die) ,(cdr form))))
(t "ouch"))
(cl-case (car form)
(filter `(seq-filter (lambda (die) ,(append (cdr form) '(die)))))
(reducer `(seq-reduce (lambda (acc die) ,(append (cdr form) '(die)))))
(adjuster `(seq-map (lambda (die) ,(append (cdr form) '(die)))))
(t "ouch"))))
Where something like:
(rpgtk--roll-mod-dispatcher '(filter evenp))
Basic validation:
(should (equal
(rpgtk--roll-mod-dispatcher '(adjuster + 3))
(rpgtk--roll-mod-dispatcher '(adjuster + 3 die))))
Top-level validation:
(rpgtk-roll-mods '(1 2 3)
'(filter evenp)
'(adjuster + 3))
Not quite right!
(defun rpgtk-roll-mods (dice-pool &rest forms)
(cons 'thread-last
(cons 'dice-pool
(seq-map 'rpgtk--roll-mod-dispatcher forms)))))
Top-level validation, again:
(rpgtk-roll-mods '(1 2 3)
'(filter evenp)
'(adjuster + 3))
That’s it. Now for real:
(defmacro rpgtk-roll-mods (dice-pool &rest forms)
(cons 'thread-last
(cons dice-pool
(seq-map 'rpgtk--roll-mod-dispatcher forms)))))
And a basic test:
(rpgtk-roll-mods '(1 2 3)
(filter evenp)
(adjuster + 3))
But … I want to store the results of each transformation.
(defun thread-last-keep (initial-lst &rest forms)
(let* ((reducer (lambda (acc form)
(cons (funcall form (car acc)) acc))))
(seq-reverse
(seq-reduce reducer forms (list initial-lst)))))
Validate:
(thread-last-keep '(1 2 3)
(lambda (lst) (seq-filter (lambda (d) (evenp d)) lst))
(lambda (lst) (seq-map (lambda (d) (+ 4 d)) lst)))
Version 2 where the lambdas are extracted:
(defun thread-last-keep (initial-lst &rest forms)
(let* ((reducer (lambda (acc form)
(cons
`((lambda (d) ,form) (car acc))
acc))))
(seq-reverse
(seq-reduce reducer forms (list initial-lst)))))
(thread-last-keep '(1 2 3)
'(seq-filter (lambda (d) (evenp d)) lst)
'(seq-map (lambda (d) (+ 4 d)) lst))
Macro that guy:
(defun thread-last-keep (initial-lst &rest forms)
`(seq-reverse
(seq-reduce (lambda (acc form)
(cons (funcall form (car acc)) acc))
,forms ,(list initial-lst))))
(thread-last-keep '(1 2 3)
(lambda (lst) (seq-filter (lambda (d) (evenp d)) lst))
(lambda (lst) (seq-map (lambda (d) (+ 4 d)) lst)))
(seq-reverse
(seq-reduce
(lambda
(acc form)
(cons
(funcall form
(car acc))
acc))
((lambda
(lst)
(seq-filter
(lambda
(d)
(evenp d))
lst))
(lambda
(lst)
(seq-map
(lambda
(d)
(+ 4 d))
lst)))
((1 2 3))))
Can we get the correct results by replacing it? Let’s start with validating it from a function:
(defun rpgtk-roll-mods (dice-pool &rest forms)
(cons 'thread-last-keep
(cons 'dice-pool
(seq-map 'rpgtk--roll-mod-dispatcher forms))))
(defmacro rpgtk-roll-mods (dice-pool &rest forms)
(cons 'thread-last-keep
(cons dice-pool
(seq-map 'rpgtk--roll-mod-dispatcher forms))))
And the validator:
(rpgtk-roll-mods '(1 2 3)
(filter evenp)
(adjuster + 3))
(filter evenp) -> (seq-filter (lambda (die) (evenp die)) dice) (adjust + 2) -> (seq-map (lambda (die) (+ 2 die)) dice) (reduce +) -> (seq-reduce (lambda (acc die) (+ acc die)) dice 0) (rpgtk--roll-mod-dispatcher '(reduce +))
(defun rpgtk-dice--roll-mod-dispatcher (form)
"Return an expanded version of FORM.
Implements DSL for `rpgtk-dice-roll-mod'."
(unless (seq-contains-p form 'die)
(setq form (append form '(die))))
(cl-case (car form)
(filter `(seq-filter (lambda (die) ,(cdr form)) dice))
(remove `(seq-remove (lambda (die) ,(cdr form)) dice))
(reduce `(list (seq-reduce (lambda (acc die) ,(append (cdr form) '(acc))) dice 0)))
(adjust `(seq-map (lambda (die) ,(cdr form)) dice))
(t "ouch"))
(cl-case (car form)
(sum `(list (seq-reduce (lambda (acc die) (+ acc die)) dice 0)))
(- `(seq-map (lambda (die) '(- die ,(cdr form)))) dice)
(filter `(seq-filter (lambda (die) ,(append (cdr form) '(die))) dice))
(remove `(seq-remove (lambda (die) ,(append (cdr form) '(die))) dice))
(reduce `(list (seq-reduce (lambda (acc die) ,(append (cdr form) '(acc die))) dice 0)))
(adjust `(seq-map (lambda (die) ,(append (cdr form) '(die))) dice))
(t "ouch"))))
(defun rpgtk-dice--roll-mods (dice forms)
(when forms
(let ((new-dice (eval (rpgtk--roll-mod-dispatcher (car forms)))))
(cons new-dice
(rpgtk--roll-mods new-dice (cdr forms))))))
;; (defun rpgtk-roll-mods (dice-pool &rest forms)
;; "Do it"
;; (cons dice-pool
;; (rpgtk--roll-mods dice-pool forms)))
(defmacro rpgtk-dice-roll-mod (dice-pool &rest forms)
"Transform a list of integers, DICE-POOL, through FORMS.
Where each form can be one of the following:
(filter func)
"
`(cons ,dice-pool
(rpgtk-dice--roll-mods ,dice-pool ',forms)))
(defun rpgtk-dice-display-roll (roll-of-nums)
"Convert ROLLS from a list of lists of integers to a string."
(let ((roll-of-strs (seq-map 'number-to-string roll-of-nums)))
(concat "「" (string-join roll-of-strs " ") "」")))
(defun rpgtk-dice-display (rolls)
"Convert ROLLS from a list of lists of integers to a string.
Given: ((3 4 1 2) (4 2) (6) (-3))
Return: 「3 4 1 2」→「4 2」→「6」→「-3」 "
(let ((s-rolls (seq-map 'rpgtk-dice-display-roll rolls)))
(string-join s-rolls "→")))
(rpgtk-dice-display
(rpgtk-dice-roll-mod '(3 4 1 2)
(filter evenp)
(reduce +)
(adjust - 3)))
(rpgtk-dice-display
(rpgtk-dice-roll-mod '(3 4 1 2)
(filter eq 0 (mod die 3))
(reduce +)
(adjust - 3)))
(mod 8 3)