Skip to content

Commit 7c96df1

Browse files
committed
Merge pull request #412 from mpenet/feature/arrows
add *-> threading macros
2 parents da5e88d + 62cacac commit 7c96df1

2 files changed

Lines changed: 156 additions & 34 deletions

File tree

pixie/stdlib.pxi

Lines changed: 111 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@
226226
([val coll]
227227
(transduce (interpose val) conj coll))))
228228

229-
230229
(def preserving-reduced
231230
(fn preserving-reduced [rf]
232231
(fn pr-inner [a b]
@@ -480,38 +479,6 @@
480479
(let [nm (with-meta nm (assoc (meta nm) :private true))]
481480
(cons `defn (cons nm rest))))
482481

483-
(defmacro ->
484-
{:doc "Threads `x` through `forms`, passing the result of one step as the first argument of the next."
485-
:examples [["(-> 3 inc inc)" nil 5]
486-
["(-> \"James\" (str \" is \" \"awesome \") (str \"(and stuff)\" \"!\"))" nil "James is awesome (and stuff)!"]]
487-
:signatures [[x & forms]]
488-
:added "0.1"}
489-
[x & forms]
490-
(loop [x x, forms forms]
491-
(if forms
492-
(let [form (first forms)
493-
threaded (if (seq? form)
494-
(with-meta `(~(first form) ~x ~@(next form)) (meta form))
495-
(list form x))]
496-
(recur threaded (next forms)))
497-
x)))
498-
499-
(defmacro ->>
500-
{:doc "Threads `x` through `forms`, passing the result of one step as the last argument of the next."
501-
:examples [["(->> \"James\" (str \"we \" \"like \") (str \"you \" \"know \" \"what? \"))" nil "you know what? we like James"]
502-
["(->> 5 (range) (map inc) seq)" nil (1 2 3 4 5)]]
503-
:signatures [[x & forms]]
504-
:added "0.1"}
505-
[x & forms]
506-
(loop [x x, forms forms]
507-
(if forms
508-
(let [form (first forms)
509-
threaded (if (seq? form)
510-
(with-meta `(~(first form) ~@(next form) ~x) (meta form))
511-
(list form x))]
512-
(recur threaded (next forms)))
513-
x)))
514-
515482
(defn not
516483
{:doc "Inverts the input, if a truthy value is supplied, returns false, otherwise
517484
returns true"
@@ -2640,6 +2607,117 @@ Calling this function on something that is not ISeqable returns a seq with that
26402607
[x]
26412608
(instance? Bool x))
26422609

2610+
(defmacro ->
2611+
{:doc "Threads `x` through `forms`, passing the result of one step as the first argument of the next."
2612+
:examples [["(-> 3 inc inc)" nil 5]
2613+
["(-> \"James\" (str \" is \" \"awesome \") (str \"(and stuff)\" \"!\"))" nil "James is awesome (and stuff)!"]]
2614+
:signatures [[x & forms]]
2615+
:added "0.1"}
2616+
[x & forms]
2617+
(loop [x x, forms forms]
2618+
(if forms
2619+
(let [form (first forms)
2620+
threaded (if (seq? form)
2621+
(with-meta `(~(first form) ~x ~@(next form)) (meta form))
2622+
(list form x))]
2623+
(recur threaded (next forms)))
2624+
x)))
2625+
2626+
(defmacro ->>
2627+
{:doc "Threads `x` through `forms`, passing the result of one step as the last argument of the next."
2628+
:examples [["(->> \"James\" (str \"we \" \"like \") (str \"you \" \"know \" \"what? \"))" nil "you know what? we like James"]
2629+
["(->> 5 (range) (map inc) seq)" nil (1 2 3 4 5)]]
2630+
:signatures [[x & forms]]
2631+
:added "0.1"}
2632+
[x & forms]
2633+
(loop [x x, forms forms]
2634+
(if forms
2635+
(let [form (first forms)
2636+
threaded (if (seq? form)
2637+
(with-meta `(~(first form) ~@(next form) ~x) (meta form))
2638+
(list form x))]
2639+
(recur threaded (next forms)))
2640+
x)))
2641+
2642+
(defmacro some->
2643+
{:doc "When expr is not nil, threads it into the first form (via ->),
2644+
and when that result is not nil, through the next etc"
2645+
:signatures [[expr & forms]]
2646+
:added "0.1"}
2647+
[expr & forms]
2648+
(let [g (gensym)
2649+
steps (map (fn [step] `(if (nil? ~g) nil (-> ~g ~step)))
2650+
forms)]
2651+
`(let [~g ~expr
2652+
~@(interleave (repeat g) (butlast steps))]
2653+
~(if (empty? steps)
2654+
g
2655+
(last steps)))))
2656+
2657+
(defmacro some->>
2658+
{:doc "When expr is not nil, threads it into the first form (via ->>),
2659+
and when that result is not nil, through the next etc"
2660+
:signatures [[x & forms]]
2661+
:added "0,1"}
2662+
[expr & forms]
2663+
(let [g (gensym)
2664+
steps (map (fn [step] `(if (nil? ~g) nil (->> ~g ~step)))
2665+
forms)]
2666+
`(let [~g ~expr
2667+
~@(interleave (repeat g) (butlast steps))]
2668+
~(if (empty? steps)
2669+
g
2670+
(last steps)))))
2671+
2672+
(defmacro cond->
2673+
{:added "0.1"
2674+
:signatures [[expr & clauses]]
2675+
:doc "Takes an expression and a set of test/form pairs. Threads expr (via ->)
2676+
through each form for which the corresponding test
2677+
expression is true. Note that, unlike cond branching, cond-> threading does
2678+
not short circuit after the first true test expression."}
2679+
[expr & clauses]
2680+
(assert (even? (count clauses)))
2681+
(let [g (gensym)
2682+
steps (map (fn [[test step]] `(if ~test (-> ~g ~step) ~g))
2683+
(partition 2 clauses))]
2684+
`(let [~g ~expr
2685+
~@(interleave (repeat g) (butlast steps))]
2686+
~(if (empty? steps)
2687+
g
2688+
(last steps)))))
2689+
2690+
(defmacro cond->>
2691+
{:doc "Takes an expression and a set of test/form pairs. Threads expr (via ->>)
2692+
through each form for which the corresponding test expression
2693+
is true. Note that, unlike cond branching, cond->> threading does not short circuit
2694+
after the first true test expression."
2695+
:signatures [[expr & clauses]]
2696+
:added "0.1"}
2697+
[expr & clauses]
2698+
(assert (even? (count clauses)))
2699+
(let [g (gensym)
2700+
steps (map (fn [[test step]] `(if ~test (->> ~g ~step) ~g))
2701+
(partition 2 clauses))]
2702+
`(let [~g ~expr
2703+
~@(interleave (repeat g) (butlast steps))]
2704+
~(if (empty? steps)
2705+
g
2706+
(last steps)))))
2707+
2708+
(defmacro as->
2709+
{:doc "Binds name to expr, evaluates the first form in the lexical context
2710+
of that binding, then binds name to that result, repeating for each
2711+
successive form, returning the result of the last form."
2712+
:signatures [[expr name & forms]]
2713+
:added "0,1"}
2714+
[expr name & forms]
2715+
`(let [~name ~expr
2716+
~@(interleave (repeat name) (butlast forms))]
2717+
~(if (empty? forms)
2718+
name
2719+
(last forms))))
2720+
26432721
(defprotocol IComparable
26442722
(-compare [x y]
26452723
"Compare to objects returing 0 if the same -1 with x is logically smaller than y and 1 if x is logically larger"))

tests/pixie/tests/test-macros.pxi

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,48 @@
55
(let [x 10 k :boop]
66
(t/assert= (-eq `{:x ~x} {:x 10}) true)
77
(t/assert= (-eq `{~k ~x} {:boop 10}) true)
8-
(t/assert= (-eq `{:x {:y ~x}} {:x {:y 10}}) true)))
8+
(t/assert= (-eq `{:x {:y ~x}} {:x {:y 10}}) true)))
9+
10+
(def constantly-nil (constantly nil))
11+
12+
(t/deftest some->test
13+
(t/assert (nil? (some-> nil)))
14+
(t/assert (= 0 (some-> 0)))
15+
(t/assert (= -1 (some-> 1 (- 2))))
16+
(t/assert (nil? (some-> 1 constantly-nil (- 2)))))
17+
18+
(t/deftest some->>test
19+
(t/assert (nil? (some->> nil)))
20+
(t/assert (= 0 (some->> 0)))
21+
(t/assert (= 1 (some->> 1 (- 2))))
22+
(t/assert (nil? (some->> 1 constantly-nil (- 2)))))
23+
24+
(t/deftest cond->test
25+
(t/assert (= 0 (cond-> 0)))
26+
(t/assert (= -1 (cond-> 0 true inc true (- 2))))
27+
(t/assert (= 0 (cond-> 0 false inc)))
28+
(t/assert (= -1 (cond-> 1 true (- 2) false inc))))
29+
30+
(t/deftest cond->>test
31+
(t/assert (= 0 (cond->> 0)))
32+
(t/assert (= 1 (cond->> 0 true inc true (- 2))))
33+
(t/assert (= 0 (cond->> 0 false inc)))
34+
(t/assert (= 1 (cond->> 1 true (- 2) false inc))))
35+
36+
(t/deftest as->test
37+
(t/assert (= 0 (as-> 0 x)))
38+
(t/assert (= 1 (as-> 0 x (inc x))))
39+
(t/assert (= 2 (as-> [0 1] x
40+
(map inc x)
41+
(reverse x)
42+
(first x)))))
43+
44+
(t/deftest threading-loop-recur
45+
(t/assert (nil? (loop []
46+
(as-> 0 x
47+
(when-not (zero? x)
48+
(recur))))))
49+
(t/assert (nil? (loop [x nil] (some-> x recur))))
50+
(t/assert (nil? (loop [x nil] (some->> x recur))))
51+
(t/assert (= 0 (loop [x 0] (cond-> x false recur))))
52+
(t/assert (= 0 (loop [x 0] (cond->> x false recur)))))

0 commit comments

Comments
 (0)