-
Notifications
You must be signed in to change notification settings - Fork 0
/
core.clj
110 lines (100 loc) · 4.12 KB
/
core.clj
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
(ns serum.core
(:require [slingshot.slingshot :refer [throw+]]))
(defmacro and-let-core [bindings expr]
(if (seq bindings)
`(if-let [~(first bindings) ~(second bindings)]
(and-let-core ~(drop 2 bindings) ~expr))
expr))
(defmacro and-let-else-core [bindings expr else-expr]
(if (seq bindings)
`(if-let
[~(first bindings) ~(second bindings)]
(and-let-else-core ~(drop 2 bindings) ~expr ~else-expr)
~else-expr)
expr))
;; use nested macro for now until I can figure out multi-arity macro/function interaction here
;; small divergence from Scheme's and-let as it supports an else expression
(defmacro and-let
"Derived from Scheme's and-let, this macro will evaluate 'expr' should all of its bindings evaluate truthy.
Evaluates 'else-expr' if any of the bindings evaluate falsey. Returns nil if 'else-expr' is not provided.
Evaluation of bindings ceases upon a falsey result.
'bindings' expects a binding-forms vector as in (let) and (loop) etc.
'expr' expression to be evaluated if bindings evaluate truthy
'else-expr' expression to be evaluated if any bindings evaluate falsey"
([bindings expr] `(and-let-core ~bindings ~expr))
([bindings expr else-expr] `(and-let-else-core ~bindings ~expr ~else-expr)))
(defn shift
"similar to partial, provides a modified interface to function 'f'.
returns a function which accepts a single argument, always passing it as the first argument of 'f'.
useful with (comp) and threading macros."
([f] f)
([f arg2] (fn [arg1] (f arg1 arg2)))
([f arg2 arg3] (fn [arg1] (f arg1 arg2 arg3)))
([f arg2 arg3 arg4 & args] (fn [arg1] (apply f arg1 arg2 arg3 arg4 args))))
(defmacro success-let
"conditional let form which expects binding expression to return a hashmap with {:success boolean} content.
'success-let' supports a single binding pair: 'binding'.
will evaluate 'expr' if (:success y#) is truthy.
will evaluate 'else' if (:success y#) is falsey.
passes binding symbol/value to both 'expr' and 'else-expr' expressions."
([binding expr]
`(success-let ~binding ~expr nil))
([binding expr else-expr]
(let [bsym (binding 0)
bexpr (binding 1)]
`(let [y# ~bexpr
~bsym y#]
(when (not (contains? y# :success))
(throw+ {:message "form evaluation result did not contain :success key"}))
(if (:success y#) ~expr ~else-expr)))))
;; TODO improve to allow multiple forms as in when-let
(defmacro fail-let
"conditional let form which expects binding expression to return a hashmap with {:success boolean} content.
'fail-let' supports a single binding pair: 'binding'.
will evaluate 'expr' if (:success y#) is falsey.
passes binding symbol/value to 'expr'."
[binding expr]
(let [bsym (binding 0)
bexpr (binding 1)]
`(let [y# ~bexpr
~bsym y#]
(when (not (contains? y# :success))
(throw+ {:message "form evaluation result did not contain :success key"}))
(when (not (:success y#)) ~expr))))
(defmacro try-true?
"the expression 'expr' is expected to be a validation form which returns booleanness.
'expr' will be executed in a try/catch form and a boolean returned.
caught exceptions will return false."
[expr]
`(try
(if ~expr
true
false)
(catch Exception e# false)))
(defmacro attempt
"the expression 'expr' will be executed in a try/catch form.
if an exception is caught, a handler function of one argument, 'f', will be executed
and will be passed the caught java exception object."
[expr f]
`(try
~expr
(catch Exception e# (~f e#))))
(defmacro success->
[x & forms]
(if forms
(let [form (first forms)
cur (if (seq? form)
`(~(first form) ~x ~@(next form))
(list form x))]
`(success-let
[y# ~cur]
(success-> y# ~@(next forms))
y#))
x))
(defmacro divert
"derived from (with-out-str). Evaluates body, captures any output destined to *out*, and returns result
of body and text output as a vector tuple, [~@body captured-output-string]"
[& body]
`(let [s# (new java.io.StringWriter)]
(binding [*out* s#]
[~@body (str s#)])))