A powerful and elegant pipelining and mapping library for the Red programming language, designed to write clean, functional, and expressive data transformation code.
Write code that reads like a sentence, not a puzzle.
- 🏗️ Chainable Pipelining: Transform data through a series of operations with the
|>
operator. - 🗺️ Chainable Mapping: Apply transformations to every element in a series with the
==>
operator. - 🔀 Seamless Mixing: Combine piping and mapping in a single, fluent chain.
- 🔍 Inline Filtering: Use the
filter
function within your chains to easily select elements. - ➡️ Left-to-Right Assignment: Assign results to variables with the intuitive
-->
operator. - ⚙️ Flexible Actions: Use functions, code blocks, or values directly in your chains.
- 🧩 Explicit Side-Effects: Perform debugging or logging without breaking the chain.
(from AI)
Feature | This Library | Haskell | Elixir |
---|---|---|---|
Pipe operator | value |> action (generic, works with funcs, blocks, literals) |
No built-in, use function composition (.) or custom (&>) |
value |> func (macro expands to func(value) ) |
Map chaining | value ==> action1 ==> action2 (series only, placeholder _m ) |
map f . map g $ xs (function composition) |
Enum.map(xs, &f/1) |> Enum.map(&g/1) |
Placeholder substitution | _p (pipe), _m (map element), (filter element), also implicit [+ val] |
Not built-in, use lambdas \x -> ... |
Capture operator &(&1 + 1) |
Filtering in chain | |> [filter _p [_e > 5]] (works in pipe and map seamlessly) |
filter (>5) xs (separate call, not inline in map chain) |
xs |> Enum.filter(&(&1 > 5)) |
Side-effects | Explicit: must include value in block ( [print ["***" _m "***"] _m] ) |
Pure by default, side-effects only inIO monad |
Explicit IO, usually last in chain |
Pipe-style assignment | value --> var (syntactic sugar for set var value ) |
N/A | N/A |
Literal replacement | Pipe/map actions can be literal values, replacing current value |
Not possible (type mismatch) | Not possible |
Mix pipe+map seamlessly | Yes (init |> f ==> g |> h ) |
Possible but verbose | Yes (xs |> Enum.map(&f/1) ) |
Feature | This Library | Clojure | F# |
---|---|---|---|
Pipe operator | value |> action (generic, works with funcs, blocks, literals) |
Threading macros:-> , ->> |
|> built-in, same as Elixir |
Map chaining | value ==> action1 ==> action2 (series only, placeholder _m ) |
(->> xs (map f) (map g)) |
xs |> List.map f |> List.map g |
Placeholder substitution | _p (pipe), _m (map element), (filter element), also implicit [+ val] |
Anonymous fn literal:#(+ % 1) |
No implicit, must use fun x -> ... |
Filtering in chain | |> [filter _p [_e > 5]] (works in pipe and map seamlessly) |
(->> xs (filter #(> % 5))) |
xs |> List.filter (fun x -> x > 5) |
Side-effects | Explicit: must include value in block ( [print ["***" _m "***"] _m] ) |
Explicit side-effects (printing/logging functions) |
Explicit, but side-effects are allowed |
Pipe-style assignment | value --> var (syntactic sugar for set var value ) |
N/A | N/A |
Literal replacement | Pipe/map actions can be literal values, replacing current value |
Not possible | Not possible |
Mix pipe+map seamlessly | Yes (init |> f ==> g |> h ) |
Yes ((->> xs (map f) (filter g)) ) |
Yes (xs |> List.map f ) |
(from AI)
- Start with a list of numbers.
- Double them (
map
). - Print each doubled value (
side-effect
). - Filter out values ≤ 5.
- Convert them to strings.
- Join into a single string.
- Assign/store the result.
[1 2 3 4 5]
==> [* 2] ; map: double
==> [print ["doubled:" _m] _m] ; side-effect (must return)
==> [filter _m [_e > 5]] ; filter
==> to-string ; map: convert to string
|> rejoin ; pipe: join
--> result ; assign (pipe-style)
print ["Result:" result]
✅ Fully left-to-right, mixing ==>
and |>
seamlessly, with explicit assignment.
import Control.Monad (forM_)
import Data.List (intercalate)
main = do
let xs = [1,2,3,4,5]
doubled = map (*2) xs
forM_ doubled (\x -> putStrLn ("doubled: " ++ show x))
let filtered = filter (>5) doubled
result = intercalate "" (map show filtered)
putStrLn ("Result: " ++ result)
➡ Verbose because of the IO monad barrier. Pure transformations vs. side-effects are separate steps.
result =
[1, 2, 3, 4, 5]
|> Enum.map(&(&1 * 2))
|> Enum.map(fn x -> IO.inspect(x, label: "doubled"); x end)
|> Enum.filter(&(&1 > 5))
|> Enum.map(&Integer.to_string/1)
|> Enum.join("")
IO.puts("Result: #{result}")
➡ Side-effects (IO.inspect
) sit cleanly inside the chain. No need to “break” the pipeline.
(def result
(->> [1 2 3 4 5]
(map #(* 2 %))
(map #(do (println "doubled:" %) %))
(filter #(> % 5))
(map str)
(apply str))) ; join
(println "Result:" result)
➡ do
handles side-effects inline. Uses (apply str)
for joining.
let result =
[1; 2; 3; 4; 5]
|> List.map (fun x -> x * 2)
|> List.map (fun x -> printfn "doubled: %d" x; x)
|> List.filter (fun x -> x > 5)
|> List.map string
|> String.concat ""
printfn "Result: %s" result
➡ Inline logging via ;
is natural. Assignment (let
) is explicit.
-
Red:
- Most fluid: mixing
map
,pipe
, filters, side-effects, and assignment in one chain. - Assignment operator (
-->
) is unique — others break the chain to bind variables.
- Most fluid: mixing
-
Haskell: clean purity but heavy ceremony — feels more “academic”.
-
Elixir: the closest in ergonomics — pipelines flow like Red, with
IO.inspect
for taps. -
Clojure: very flexible; threading macros make it short, but
do
feels heavier. -
F#: close to Elixir in readability; concise side-effects with
;
.
👉 Honestly, your Red DSL feels like Elixir pipelines + F# assignment + Clojure placeholders all rolled into one, but with more scripting freedom (e.g. literals, pipe-style assignment).
- Download the
pipe-map.red
file. - Include it in your Red program:
#include %pipe-map.red
Pass a value through a series of operations.
"Red is rocking!" |> uppercase |> [split _p " "] |> probe
; ["RED" "IS" "ROCKING!"]
Transform each element in a series.
[1 2 3 4 5] ==> [* 2] ==> [+ 10] ==> probe
; [12 14 16 18 20]
Process data through a combination of steps.
"hello world"
|> [split _p " "] ; Pipe: Split into words -> ["hello" "world"]
==> uppercase ; Map: Uppercase each -> ["HELLO" "WORLD"]
|> reverse ; Pipe: Reverse the order -> ["WORLD" "HELLO"]
|> form ; Pipe: Form back into a string -> "WORLD HELLO"
--> result ; Assign: result --> "WORLD HELLO"
The |>
operator passes the value on its left to the action on its right.
Syntax: value |> action
Actions can be:
- A Function:
|> uppercase
- A Code Block:
- Implicit placeholder:
|> [* 2]
(equivalent to[_p * 2]
) - Explicit placeholder:
|> [_p + (sin _p)]
- Implicit placeholder:
- A Value:
|> 100
(replaces the pipe value with100
)
The ==>
operator applies the action to every element of the series on its left.
Syntax: series ==> action
The same action rules apply. Use the _m
placeholder to refer to the current element.
[1 2 3] ==> [_m * _m] ; Square each element -> [1 4 9]
Use filter
within a |>
or ==>
block to select elements. Use the _e
placeholder.
Filter a series in a pipe:
[1 2 3 4 5] |> [filter _p [_e > 3]] ; Keep elements > 3 -> [4 5]
Filter within a map (to filter each sub-series):
[ [1 2] [3 4 5] ] ==> [filter _m [_e > 2]] ; Filter each inner block -> [[] [3 4 5]]
Assign the result of a chain to a variable in a natural, left-to-right style.
"hello" |> uppercase --> shouted
print shouted ; "HELLO"
Perform actions like print
or probe
without breaking the chain by explicitly passing the value forward.
NOTE: Using probe is more convenient as it already pass the value on.
; Print the intermediate value, then pass it on
[1 2 3] ==> [* 2] |> [print _p _p] ==> [* 10]
; Output: [2 4 6]
; Result: [20 40 60]
; A JSON string transformation pipeline
convert-json: func [json][
json
|> [replace/all _p "{" ""]
|> [replace/all _p "}" ""]
|> [replace/all _p ":" " "]
|> [to-block _p]
|> [to-map _p] ; Output a map!
]
my-json: {{"name":"Alice","id":42}}
my-map: convert-json my-json
probe my-map
; #["name" "Alice" "id" 42]
(number-gen 100 1000 50) --> nums
|> sort --> sorted
|> [moving-average _p 5] --> smooth
==> [/ 1000] --> normalized
|> variance --> var
|> sqrt --> sd
print ["Raw:" take nums 10]
print ["Sorted:" take sorted 10]
print ["Smoothed:" take smooth 10]
print ["Normalized:" take normalized 10]
print ["Std Dev:" sd]
Operator/Function | Description | Placeholder |
---|---|---|
value |>action` |
Pipe value through actions |
_p |
series ==> action |
Maps action over each element in series . |
_m |
[filter list [cond]] |
Filters list based on the condition block. |
_p _e or _m _e |
value --> var |
Assigns value to the word var . |
Found a bug or have an idea? Feel free to open an issue or a pull request on GitHub!
This library is provided under the MIT License.