(.module: {#.doc "Composable extensions to the piping macros (|> and <|) that enhance them with various abilities."} lux (lux (control [monad #+ do] ["p" parser]) (data ["e" error] (coll [list #+ "list/" Fold Monad])) [macro #+ with-gensyms] (macro ["s" syntax #+ syntax: Syntax] [code]) )) ## [Syntax] (def: body^ (Syntax (List Code)) (s.tuple (p.some s.any))) (syntax: #export (new> {tokens (p.at-least +2 s.any)}) {#.doc (doc "Ignores the piped argument, and begins a new pipe." (|> 20 (i/* 3) (i/+ 4) (new> 0 inc)))} (case (list.reverse tokens) (^ (list& _ r-body)) (wrap (list (` (|> (~+ (list.reverse r-body)))))) _ (undefined))) (syntax: #export (let> binding body prev) {#.doc (doc "Gives a name to the piped-argument, within the given expression." (|> 5 (let> X (i/+ X X))))} (wrap (list (` (let [(~ binding) (~ prev)] (~ body)))))) (def: _reverse_ (Syntax Any) (function (_ tokens) (#e.Success [(list.reverse tokens) []]))) (syntax: #export (cond> {_ _reverse_} prev {else body^} {_ _reverse_} {branches (p.some (p.seq body^ body^))}) {#.doc (doc "Branching for pipes." "Both the tests and the bodies are piped-code, and must be given inside a tuple." (|> 5 (cond> [i/even?] [(i/* 2)] [i/odd?] [(i/* 3)] [(new> -1)])))} (with-gensyms [g!temp] (wrap (list (` (with-expansions [(~ g!temp) (~ prev)] (cond (~+ (do list.Monad [[test then] branches] (list (` (|> (~ g!temp) (~+ test))) (` (|> (~ g!temp) (~+ then)))))) (|> (~ g!temp) (~+ else))))))))) (syntax: #export (if> {then body^} {else body^} prev) (wrap (list (` (cond> [] [(new> (~+ then))] [(new> (~+ else))] (~ prev)))))) (syntax: #export (loop> {test body^} {then body^} prev) {#.doc (doc "Loops for pipes." "Both the testing and calculating steps are pipes and must be given inside tuples." (|> 1 (loop> [(i/< 10)] [inc])))} (with-gensyms [g!temp] (wrap (list (` (loop [(~ g!temp) (~ prev)] (if (|> (~ g!temp) (~+ test)) ((~' recur) (|> (~ g!temp) (~+ then))) (~ g!temp)))))))) (syntax: #export (do> monad {steps (p.some body^)} prev) {#.doc (doc "Monadic pipes." "Each steps in the monadic computation is a pipe and must be given inside a tuple." (|> 5 (do> Monad [(i/* 3)] [(i/+ 4)] [inc])))} (with-gensyms [g!temp] (case (list.reverse steps) (^ (list& last-step prev-steps)) (let [step-bindings (do list.Monad [step (list.reverse prev-steps)] (list g!temp (` (|> (~ g!temp) (~+ step)))))] (wrap (list (` ((~! do) (~ monad) [(~ g!temp) (~ prev) (~+ step-bindings)] (|> (~ g!temp) (~+ last-step))))))) _ (wrap (list prev))))) (syntax: #export (exec> {body body^} prev) {#.doc (doc "Non-updating pipes." "Will generate piped computations, but their results will not be used in the larger scope." (|> 5 (exec> [.nat %n log!]) (i/* 10)))} (with-gensyms [g!temp] (wrap (list (` (let [(~ g!temp) (~ prev)] (exec (|> (~ g!temp) (~+ body)) (~ g!temp)))))))) (syntax: #export (tuple> {paths (p.many body^)} prev) {#.doc (doc "Parallel branching for pipes." "Allows to run multiple pipelines for a value and gives you a tuple of the outputs." (|> 5 (tuple> [(i/* 10)] [dec (i// 2)] [Int/encode])) "Will become: [50 2 \"5\"]")} (with-gensyms [g!temp] (wrap (list (` (let [(~ g!temp) (~ prev)] [(~+ (list/map (function (_ body) (` (|> (~ g!temp) (~+ body)))) paths))])))))) (syntax: #export (case> {branches (p.many (p.seq s.any s.any))} prev) {#.doc (doc "Pattern-matching for pipes." "The bodies of each branch are NOT pipes; just regular values." (|> 5 (case> 0 "zero" 1 "one" 2 "two" 3 "three" 4 "four" 5 "five" 6 "six" 7 "seven" 8 "eight" 9 "nine" _ "???")))} (wrap (list (` (case (~ prev) (~+ (list/join (list/map (function (_ [pattern body]) (list pattern body)) branches))))))))