Skip to content

hinjolicious/red-pipe-map

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pipe-map.red

1756903009889

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.

Red Language Version

Features

  • 🏗️ 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.

Comparison of Piping & Mapping Features

(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 in
IO 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 )

🚀 A full mixed code showcase.:

(from AI)

  1. Start with a list of numbers.
  2. Double them (map).
  3. Print each doubled value (side-effect).
  4. Filter out values ≤ 5.
  5. Convert them to strings.
  6. Join into a single string.
  7. Assign/store the result.

🔴 Red (your lib)

[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.


🔵 Haskell

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.


🟣 Elixir

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.


🟢 Clojure

(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.


🟠 F#

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.


⚖️ Side-by-Side Takeaways

  • 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.
  • 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).

Installation

  1. Download the pipe-map.red file.
  2. Include it in your Red program:
    #include %pipe-map.red

Quick Start

Basic Piping

Pass a value through a series of operations.

"Red is rocking!" |> uppercase |> [split _p " "] |> probe
; ["RED" "IS" "ROCKING!"]

Basic Mapping

Transform each element in a series.

[1 2 3 4 5] ==> [* 2] ==> [+ 10] ==> probe
; [12 14 16 18 20]

Mixed Piping and Mapping

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"

Core Operators & Functions

The Piping Operator: |>

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)]
  • A Value: |> 100 (replaces the pipe value with 100)

The Mapping Operator: ==>

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]

The Filter Function: filter

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]]

The Assignment Operator: -->

Assign the result of a chain to a variable in a natural, left-to-right style.

"hello" |> uppercase --> shouted
print shouted ; "HELLO"

Explicit Side-Effects

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]

Detailed Examples

1. Data Processing Pipeline

; 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]

2. Complex Mathematical Chain

(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]

API Reference

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.

Contributing

Found a bug or have an idea? Feel free to open an issue or a pull request on GitHub!

License

This library is provided under the MIT License.

About

Pipelining and Mapping in Red Language

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages