(.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 i/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 Unit) (function [tokens] (#e.Success [(list.reverse tokens) []]))) (syntax: #export (cond> [_ _reverse_] prev [else body^] [_ _reverse_] [branches (p.many (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 (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)] [i/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)] [i/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> [int-to-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)] [i/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))))))))