aboutsummaryrefslogtreecommitdiff
path: root/macros.lisp
blob: bbc7f4aee3e54ffbd2b84165967272bd351a0427 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
(in-package :lambda-tools)

(eval-when (:compile-toplevel :load-toplevel :execute)
  ;;; some fucntions for workign with substitution variables

  (defun substitute-var-p (symbol)
    (and (not (eql '$$ symbol))
         (symbolp symbol)
         (eq (elt (symbol-name symbol) 0)
             #\$)))

  (defun numeric-char-p (c)
    (and (alphanumericp c) (not (alpha-char-p c))))
  
  (defun numeric-var-p (symbol)
    (and (substitute-var-p symbol)
         (numeric-char-p
          (elt (symbol-name symbol) 1))))


  (defun numerically-before-p (a b)
    (apply #'<
           (mapcar (lambda (x) (parse-integer (symbol-name x) :start 1 :junk-allowed t))
                   (list a b))))
  )





(defmacro $$ (expr)
  "Quickly create functions from an expression EXPR with 'blanks' in
it. Each blank is a symbol that betins with the underscore _. Symbols
with the same name are treated as the same variable.

A function is returned, it accepts exactly the number of variables as
there were unique blanks in the expression. When calling the new
function, the variables are bound in the order they appeared in EXPR.

This is a rather simple macro - you cannot nest $$ forms. If you try,
an error will be raised.

> (macroexpand-1 '($$ (+ $a (* $b 3) $b (- $a $c) 10)))
  (LAMBDA ($A $B $C) 
     (+ $A 
       (* $B 3) 
       $B 
       (- $A $C) 
       10))

The macro is useful for succinctly passing functions to
higher order functions:

> (mapcar ($$ (+ $ 10)) '(1 2 3 4))
 (11 12 13 14)

> (let ((elt-num 2))
    (mapcar ($ (elt $ elt-num)) 
            (list \"hey dude\" 
                  #(1 2 3 4)
                  \"ffffffffff\")))
 (#\y 3 #\f)

You can specify the order of the variable in the argument list by
nameing the variable with a number after the $ symbol. If you go this
route, all of your variables must be numbered.

E.g.

> (reduce ($$ (concatenate 'string $2-x #\Space $1-acc))
          (list \"one\" \"two\" \"three\")
          :initial-value \"zubas\")

 \"three two one zubas\"


Note that you can use any numbers, the arguments are sorted by < on
those numbers.

"
  (let  ((new-params (list))
         (numeric-params nil))
    (subst-if t (constantly nil) expr
              :key (lambda (x)
                     (when (substitute-var-p x)
                       (pushnew x new-params))
                     (when (numeric-var-p x)
                       (setf numeric-params t))
                     (when (eql '$$ x)
                       (error "$$ cannot be nested"))))
    (setf new-params
          (if numeric-params 
              (sort new-params #'numerically-before-p)
              (reverse new-params)))
    `(lambda ,new-params  ,expr)))