-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy patharr.el
236 lines (201 loc) · 8.41 KB
/
arr.el
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
;;; arr.el --- Modern threading macros -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2022 Jeetaditya Chatterjee
;;
;; Author: Jeetaditya Chatterjee <https://github.com/jeetelongname>
;; Maintainer: Jeetaditya Chatterjee <[email protected]>
;; Created: March 18, 2022
;; Modified: March 18, 2022
;; Version: 0.0.1
;; Keywords: convenience extensions lisp
;; Homepage: https://github.com/jeetelongname/el-arrows
;; Package-Requires: ((emacs "24.3"))
;;
;; This file is not part of GNU Emacs.
;;
;;; Commentary:
;;
;; An implementations of threading macros, inspired by other libraries from the
;; wider Lisp ecosystem.
;;
;;
;;; Code:
(require 'cl-lib)
;;; Normal threading macros
;;;;;; Internal
(defun arr--simple-inserter (insert-fun)
"Takes an INSERT-FUN. will return a builder function used to expand pipeline."
(lambda (acc next)
(if (listp next)
(funcall insert-fun acc next)
(list next acc))))
(defun arr--insert-first (arg surround)
"Insert ARG into the list SURROUND as its first argument, after the operator."
(cl-list* (car surround)
arg
(cdr surround)))
(defun arr--insert-last (arg surround)
"Insert ARG into the list form SURROUND as its last argument."
(append surround (list arg)))
;;;; Macros
(defmacro arr-> (initial-form &rest forms)
"Insert INITIAL-FORM as first argument into the first of FORMS.
The result into the next, etc., before evaluation.
FORMS are treated as list designators.
Identical in functionality to the builtin `thread-first'"
(cl-reduce (arr--simple-inserter #'arr--insert-first)
forms
:initial-value initial-form))
(defmacro arr->> (initial-form &rest forms)
"Like `arr->', but the INITIAL-FORM are inserted as last argument in FORMS.
Identical in functionality to the builtin `thread-last'"
(cl-reduce (arr--simple-inserter #'arr--insert-last)
forms
:initial-value initial-form))
;;; Diamond macros
;;;; Internal
(defun arr--diamond-inserter (insert-fun)
"Takes an INSERT-FUN. will return a builder function used to expand pipeline.
Takes into account placeholders."
(arr--simple-inserter (lambda (acc next)
(cl-case (cl-count-if #'arr--<>p next)
(0 (funcall insert-fun acc next))
(1 (cl-substitute-if acc #'arr--<>p next))
(t (let ((r (gensym "R")))
`(let ((,r ,acc))
,(cl-substitute-if r #'arr--<>p next))))))))
(defun arr--<>p (form)
"Predicate identifying the placeholders in FORMs for the -<> and -<>> macros."
(and (symbolp form)
(string= form "<>")))
(defmacro arr-<> (initial-form &rest forms)
"Like `arr->' but FORMS can have placeholders `<>' in an arbitrary location.
This only applys to the top level and not in INITIAL-FORM.
Each such symbol is substituted by the primary result of the form
accumulated so far, instead of it being inserted as first argument. Also known
as diamond wand."
(cl-reduce (arr--diamond-inserter #'arr--insert-first)
forms
:initial-value initial-form))
(defmacro arr-<>> (initial-form &rest forms)
"Like `arr->>' but FORMS can have placeholders `<>' in an arbitrary location.
This only applys to the top level and not in INITIAL-FORM.
Each such symbol is substituted by the primary result of the form
accumulated so far, instead of it being inserted as last argument. Also known
as diamond spear."
(cl-reduce (arr--diamond-inserter #'arr--insert-last)
forms
:initial-value initial-form))
;;; Maybe/nil short-circuiting macros
;;;; Internal
(defun arr--?-inserter (insert-fun)
"Generate reduction function for nill short circeting function.
Takes INSERT-FUN to generate actual code."
(lambda (acc next)
(cl-destructuring-bind (let* bindings var) acc
`(,let* (,@bindings
(,var (when ,var
,(funcall insert-fun var next))))
,var))))
(defun arr--expand-maybe (initial-form forms insert-fun)
"Perform nil short circting reduction.
Acts like other reductions in that it takes an INITIAL-FORM
and threads it through FORMS at the direction of INSERT-FUN."
(let ((var (gensym "maybe")))
(cl-reduce (arr--?-inserter insert-fun)
forms
:initial-value `(let* ((,var ,initial-form))
,var))))
(defmacro arr-?> (initial-form &rest forms)
"Like `arr->' but short-circuits if any FORMS, incuding INITIAL-FORM, are nil."
(arr--expand-maybe initial-form forms (arr--simple-inserter #'arr--insert-first)))
(defmacro arr-?>> (initial-form &rest forms)
"Like `arr->>' but short-circuits if any FORMS, incuding INITIAL-FORM, are nil."
(arr--expand-maybe initial-form forms (arr--simple-inserter #'arr--insert-last)))
(defmacro arr-<?> (initial-form &rest forms)
"Like `arr-<>' but short-circuits if any FORMS, incuding INITIAL-FORM, are nil."
(arr--expand-maybe initial-form forms (arr--diamond-inserter #'arr--insert-first)))
(defmacro arr-<?>> (initial-form &rest forms)
"Like `arr-<?>' but short-circuits if any FORMS, incuding INITIAL-FORM, are nil."
(arr--expand-maybe initial-form forms (arr--diamond-inserter #'arr--insert-last)))
(defmacro arr->* (&rest forms)
"Like `arr->' but the initial-form is passed in as the last in FORMS.
This is meant to be used in composition with `arr->>',
where the argument is passed in as the last one.
Example:
(arr->> 3
(/ 12)
(arr->* (/ 2)))
=> 2"
`(arr-> ,@(append (last forms) (butlast forms))))
(defmacro arr-<>* (&rest forms)
"Like `arr-<>' but the initial-form is passed in as the last in FORMS.
This is meant to be used in compostion with `arr->>',
See `arr->*' for a full explaination."
`(arr-<> ,@(append (last forms) (butlast forms))))
(defmacro arr-as-> (initial-form var &rest forms)
"Thread INITIAL-FORM through FORMS as VAR to there successor.
Note that unlike the other threading macros that every call needs to
explicitly use the variable."
`(let* ,(mapcar (lambda (form)
(list var form))
(cons initial-form forms))
,var))
(defmacro arr-as->* (name &rest forms)
"Like `arr-as->' but the initial-form is passed in as the last in FORMS.
NAME is taken as the first argument before the FORMS.
This is meant to be used in composition with `arr->>',
See `arr->*' for a full explaination."
`(arr-as-> ,@(append (last forms) name (butlast forms))))
;;; fn varients
(defmacro arr-fn-> (&rest forms)
"Return a `lambda' that threads its argument through FORMS using `arr->'."
`(lambda (x)
(arr-> x ,@forms)))
(defmacro arr-fn->> (&rest forms)
"Return a `lambda' that threads its argument through FORMS using `arr->>'."
`(lambda (x)
(arr->> x ,@forms)))
(defmacro arr-fn-<> (&rest forms)
"Return a `lambda' that threads its argument through FORMS using `arr-<>'."
`(lambda (x)
(arr-<> x ,@forms)))
(defmacro arr-fn-<>> (&rest forms)
"Return a `lambda' that threads its argument through FORMS using `arr-<>>'."
`(lambda (x)
(arr-<>> x ,@forms)))
(defmacro arr-fn-?> (&rest forms)
"Return a `lambda' that threads its argument through FORMS using `arr-?>'."
`(lambda (x)
(arr-?> x ,@forms)))
(defmacro arr-fn-?>> (&rest forms)
"Return a `lambda' that threads its argument through FORMS using `arr-?>>'."
`(lambda (x)
(arr-?>> x ,@forms)))
(defmacro arr-fn-<?> (&rest forms)
"Return a `lambda' that threads its argument through FORMS using `arr-<?>'."
`(lambda (x)
(arr-<?> x ,@forms)))
(defmacro arr-fn-<?>> (&rest forms)
"Return a `lambda' that threads its argument through FORMS using `arr-<?>>'."
`(lambda (x)
(arr-<?>> x ,@forms)))
(defmacro arr-fn-as-> (name &rest forms)
"Given NAME, yield `lambda' that threads its arg through FORMS using `arr-as->'."
`(lambda (x)
(arr-as-> x ,name ,@forms)))
;;; helper functions
(cl-defun arr-inspect (value &optional &key print-fn label)
"Like the `identity' function but will allow for printing of the VALUE.
Can have an optional LABEL to identify inspect calls.
Useful when debugging pipelines.
pass in your own PRINT-FN to use other interfaces.
PRINT-FN needs to take in value and label in that order.
the return value is discarded.
Prints to *Messages* by default."
(if print-fn
(funcall print-fn value label)
(message "%s: %s" (if label "value" label) (prin1-to-string value)))
value)
(provide 'arr)
;;; arr.el ends here