Skip to content

Commit a9056a2

Browse files
authored
Threading macros (#284)
* Threading macros * Conditional thread macros * Thread macro tests * Simplify macro logic
1 parent a3fd2cc commit a9056a2

File tree

3 files changed

+193
-4
lines changed

3 files changed

+193
-4
lines changed

src/basilisp/core/__init__.lpy

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@
125125
(fn vector? [o]
126126
(instance? basilisp.lang.vector/Vector o)))
127127

128+
(def
129+
^{:doc "Return true if o implements Seq."}
130+
seq?
131+
(fn seq? [o]
132+
(instance? basilisp.lang.seq/Seq o)))
133+
128134
(def seq
129135
(fn seq [o]
130136
(basilisp.lang.runtime/to-seq o)))
@@ -826,10 +832,15 @@
826832

827833
(defn map
828834
"Return a lazy sequence of (f elem) for elements in coll."
829-
[f coll]
830-
(lazy-seq
831-
(when (seq coll)
832-
(cons (f (first coll)) (map f (rest coll))))))
835+
([f coll]
836+
(lazy-seq
837+
(when (seq coll)
838+
(cons (f (first coll)) (map f (rest coll))))))
839+
([f coll & colls]
840+
(lazy-seq
841+
(when (and (seq coll) (every? seq colls))
842+
(cons (apply f (first coll) (map first colls))
843+
(apply map f (rest coll) (map rest colls)))))))
833844

834845
(defn filter
835846
"Return elements from coll where (pred elem) returns true."
@@ -883,6 +894,13 @@
883894
(drop-while pred (rest coll))
884895
(seq coll)))))
885896

897+
(defn drop-last
898+
"Drop all but the last n items of coll."
899+
([coll]
900+
(drop-last 1 coll))
901+
([n coll]
902+
(map (comp first vector) coll (drop n coll))))
903+
886904
(defn split-at
887905
"Split a collection at the nth item. Returns a vector of
888906
[(take n coll) (drop n coll)]."
@@ -1011,6 +1029,98 @@
10111029
(finally
10121030
(. (var ~vvar) ~'pop-bindings)))))
10131031

1032+
;;;;;;;;;;;;;;;;;;;;;;
1033+
;; Threading Macros ;;
1034+
;;;;;;;;;;;;;;;;;;;;;;
1035+
1036+
(defmacro ->
1037+
"Thread x through the forms. Places x in the second position of
1038+
the first form, and then the resulting expression into the second
1039+
position of the second form, etc. Forms which are not lists will
1040+
be made into lists."
1041+
[x & forms]
1042+
(if (seq forms)
1043+
(let [joining (first forms)]
1044+
`(->
1045+
~(if (seq? joining)
1046+
(apply list (first joining) x (rest joining))
1047+
(list joining x))
1048+
~@(rest forms)))
1049+
x))
1050+
1051+
(defmacro ->>
1052+
"Thread x through the forms. Places x in the last position of
1053+
the first form, and then the resulting expression into the last
1054+
position of the second form, etc. Forms which are not lists will
1055+
be made into lists."
1056+
[x & forms]
1057+
(if (seq forms)
1058+
(let [joining (first forms)]
1059+
`(->>
1060+
~(if (seq? joining)
1061+
(concat joining (list x))
1062+
(list joining x))
1063+
~@(rest forms)))
1064+
x))
1065+
1066+
(defmacro some->
1067+
"Thread x through the forms (as by ->) until the resulting expression
1068+
is nil or there are no more forms."
1069+
[x & forms]
1070+
(if (seq forms)
1071+
`(let [result# (-> ~x ~(first forms))]
1072+
(when-not (nil? result#)
1073+
(some-> result# ~@(next forms))))
1074+
x))
1075+
1076+
(defmacro some->>
1077+
"Thread x through the forms (as by ->>) until the resulting expression
1078+
is nil or there are no more forms."
1079+
[x & forms]
1080+
(if (seq forms)
1081+
`(let [result# (->> ~x ~(first forms))]
1082+
(when-not (nil? result#)
1083+
(some->> result# ~@(next forms))))
1084+
x))
1085+
1086+
(defmacro cond->
1087+
"Takes a test and form pair, threading x (as by ->) through each form for
1088+
which the corresponding test evaluates as true. cond-> does not short
1089+
circuit evaluation in any case."
1090+
[x & clauses]
1091+
(if (seq clauses)
1092+
`(let [e# (if ~(first clauses)
1093+
(-> ~x ~(second clauses))
1094+
~x)]
1095+
(cond-> e#
1096+
~@(nthnext clauses 2)))
1097+
x))
1098+
1099+
(defmacro cond->>
1100+
"Takes a test and form pair, threading x (as by ->>) through each form for
1101+
which the corresponding test evaluates as true. cond->> does not short
1102+
circuit evaluation in any case."
1103+
[x & clauses]
1104+
(if (seq clauses)
1105+
`(let [e# (if ~(first clauses)
1106+
(->> ~x ~(second clauses))
1107+
~x)]
1108+
(cond->> e#
1109+
~@(nthnext clauses 2)))
1110+
x))
1111+
1112+
(defmacro as->
1113+
"Bind x to name and thread it through the forms, replacing instances of name
1114+
in forms with the threaded expression."
1115+
[x name & forms]
1116+
(if (seq forms)
1117+
`(as->
1118+
(let [~name ~x]
1119+
~(first forms))
1120+
~name
1121+
~@(next forms))
1122+
x))
1123+
10141124
;;;;;;;;;;;;;;;;;;;;;;
10151125
;; Output Utilities ;;
10161126
;;;;;;;;;;;;;;;;;;;;;;

tests/basilisp/core_macros_test.lpy

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
(ns basilisp.core-macros-test
2+
(:require
3+
[basilisp.test :refer [deftest is]]))
4+
5+
(deftest ->-test
6+
(is (= :a (-> :a)))
7+
(is (= 2 (-> 1 inc)))
8+
(is (= 1 (-> 1 inc dec)))
9+
(is (= 4 (-> 10 inc (- 7)))))
10+
11+
(deftest ->>-test
12+
(is (= :a (->> :a)))
13+
(is (= 2 (->> 1 inc)))
14+
(is (= 1 (->> 1 inc dec)))
15+
(is (= 14 (->> [1 2 3 4]
16+
(map inc)
17+
(reduce +)))))
18+
19+
(deftest some->-test
20+
(is (= :a (some-> :a)))
21+
(is (= 2 (some-> 1 inc)))
22+
(is (= 1 (some-> 1 inc dec)))
23+
(is (= 4 (some-> 10 inc (- 7))))
24+
25+
(is (= nil (some-> {:a 3} :b inc)))
26+
(is (= 4 (some-> {:a 3} :a inc))))
27+
28+
(deftest some->>-test
29+
(is (= :a (some->> :a)))
30+
(is (= 2 (some->> 1 inc)))
31+
(is (= 1 (some->> 1 inc dec)))
32+
(is (= -4 (some->> 10 inc (- 7))))
33+
34+
(is (= nil (some->> {:a 3} :b (- 7))))
35+
(is (= 5 (some->> {:a 3} :a (- 8)))))
36+
37+
(deftest cond->-test
38+
(is (= 1 (cond-> 1)))
39+
(is (= 1 (cond-> 1 false inc)))
40+
(is (= 0 (cond-> 1
41+
false inc
42+
true dec)))
43+
(is (= 4 (cond-> 10
44+
(= :a :a) inc
45+
(seq []) (* 3)
46+
true (- 7)))))
47+
48+
(deftest cond->>-test
49+
(is (= 1 (cond->> 1)))
50+
(is (= 1 (cond->> 1 false inc)))
51+
(is (= 0 (cond->> 1
52+
false inc
53+
true dec)))
54+
(is (= 9 (cond->> 10
55+
(= :a :a) inc
56+
(seq []) (* 3)
57+
true (- 20)))))
58+
59+
(deftest as->-test
60+
(is (= 10 (as-> 10 a)))
61+
(is (= 7 (as-> 18 x (- x 11))))
62+
(is (= 9 (as-> 8 x
63+
{:a x :c 0}
64+
(:a x)
65+
(inc x)))))

tests/basilisp/core_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,9 @@ def test_map():
618618
assert llist.l(1, 2, 3) == core.map_(core.identity, vec.v(1, 2, 3))
619619
assert llist.l(2, 3, 4) == core.map_(core.inc, vec.v(1, 2, 3))
620620

621+
assert llist.l(5, 7, 9) == core.map_(core.__PLUS__, vec.v(1, 2, 3), vec.v(4, 5, 6))
622+
assert llist.l(5, 7, 9) == core.map_(core.__PLUS__, vec.v(1, 2, 3), core.range_(4))
623+
621624

622625
def test_filter():
623626
assert llist.List.empty() == core.filter_(core.identity, vec.Vector.empty())
@@ -665,6 +668,17 @@ def test_drop_while():
665668
assert llist.l(2, 4, 6, 8) == core.drop_while(core.odd__Q__, vec.v(2, 4, 6, 8))
666669

667670

671+
def test_drop_last():
672+
assert llist.l(1, 2, 3, 4) == core.drop_last(vec.v(1, 2, 3, 4, 5))
673+
assert llist.l(1, 2, 3) == core.drop_last(2, vec.v(1, 2, 3, 4, 5))
674+
assert llist.l(1, 2) == core.drop_last(3, vec.v(1, 2, 3, 4, 5))
675+
assert llist.l(1) == core.drop_last(4, vec.v(1, 2, 3, 4, 5))
676+
assert llist.List.empty() == core.drop_last(5, vec.v(1, 2, 3, 4, 5))
677+
assert llist.List.empty() == core.drop_last(6, vec.v(1, 2, 3, 4, 5))
678+
assert llist.l(1, 2, 3, 4, 5) == core.drop_last(0, vec.v(1, 2, 3, 4, 5))
679+
assert llist.l(1, 2, 3, 4, 5) == core.drop_last(-1, vec.v(1, 2, 3, 4, 5))
680+
681+
668682
def test_split_at():
669683
assert vec.v(llist.List.empty(), llist.List.empty()) == core.split_at(3, vec.Vector.empty())
670684
assert vec.v(llist.List.empty(), llist.l(1, 2, 3)) == core.split_at(0, vec.v(1, 2, 3))

0 commit comments

Comments
 (0)