Skip to content

aiya000/luarrow.lua

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

158 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

[β†’] luarrow [β†’]

|> The true Pipeline-operator |>
.$ The Haskell-inspired function compositions .$
*% The new syntax for Lua, and you ^%

πŸš— Quick Examples

Powered by Lua's beautiful operator overloading (of %, *, ^), bringing you the elegance of:

  • OCaml, Julia, F#, PHP, Elixir, Elm's true pipeline operators x |> f |> g -- Unlike pipe(x, f, g) (cheap pipe function)1
    • The beauty of the pipeline operator hardly needs mentioning here
local arrow = require('luarrow').arrow

-- The **true** pipeline operator
local _ = 42
  % arrow(function(x) return x - 2 end)
  ^ arrow(function(x) return x * 10 end)
  ^ arrow(function(x) return x + 1 end)
  ^ arrow(print)  -- 401

Equivalent to: 2

// PHP
42
  |> (fn($x) => $x - 2)
  |> (fn($x) => $x * 10)
  |> (fn($x) => $x + 1)
  |> var_dump(...);
  • Haskell's highly readable f . g . h $ x syntax -- Unlike f(g(h(x))) (too many parentheses!)
    • This notation is also used in mathematics, and similarly, it is a very beautiful syntax
local fun = require('luarrow').fun

local function f(x) return x + 1 end
local function g(x) return x * 10 end
local function h(x) return x - 2 end

-- Compose and apply with Haskell-like syntax!
local result = fun(f) * fun(g) * fun(h) % 42
print(result)  -- 401

Equivalent to:

-- Haskell
print . f . g . h $ 42

Detailed documentation can be found in ./doc/ directory.

✨ Why luarrow?

Write dramatically cleaner, more expressive Lua code:

  • Beautiful code - Make your functional pipelines readable and maintainable
  • Elegant composition - Chain multiple functions naturally with */^ operators
    • True pipeline operators - Transform data with intuitive left-to-right flow x % f ^ g
    • Haskell-inspired syntax - Write f * g % x instead of f(g(x))
  • Zero dependencies - Pure Lua implementation with no external dependencies
  • Excellent performance - In LuaJIT environments (like Neovim), pre-composed functions have virtually no overhead compared to pure Lua

Note

About the name:

"luarrow" is a portmanteau of "Lua" + "arrow", where "arrow" refers to the function arrow (β†’) commonly used in mathematics and functional programming to denote functions (A β†’ B).

πŸš€ Getting Started

Pipeline-Style Composition 3

If you prefer left-to-right (β†’) data flow (like the |> operator in OCaml/Julia/F#/Elixir/Elm), use arrow, %, and ^:

local arrow = require('luarrow').arrow

-- Pipeline style: data flows left to right
local _ = 42
  % arrow(function(x) return x - 2 end)
  ^ arrow(function(x) return x * 10 end)
  ^ arrow(function(x) return x + 1 end)
  ^ arrow(print)  -- 401
-- Evaluation: minus_two(42) = 40
--             times_ten(40) = 400
--             add_one(400) = 401

Tip

Alternative styles:

You can also use these styles if you prefer:

-- Store the result and print separately
local result = 42
  % arrow(function(x) return x - 2 end)
  ^ arrow(function(x) return x * 10 end)
  ^ arrow(function(x) return x + 1 end)
print(result)  -- 401

-- Or wrap the entire pipeline in print()
print(
  42
    % arrow(function(x) return x - 2 end)
    ^ arrow(function(x) return x * 10 end)
    ^ arrow(function(x) return x + 1 end)
)  -- 401

Haskell-Style Composition

If you prefer right-to-left (←) data flow (like the . and the $ operator in Haskell), use fun, %, and *:

local fun = require('luarrow').fun

local add_one = function(x) return x + 1 end
local times_ten = function(x) return x * 10 end
local minus_two = function(x) return x - 2 end

-- Chain as many functions as you want!
local result = fun(add_one) * fun(times_ten) * fun(minus_two) % 42
print(result)  -- 401
-- Evaluation: minus_two(42) = 40
--             times_ten(40) = 400
--             add_one(400) = 401

Tip

This function composition f * g is the mathematical notation f ∘ g.

Tip

🀫 Secret Notes:
Actually, the function composition part f ^ g of the pipeline operator is also used in some areas of mathematics as f ; g.

Pipeline-Style vs Haskell-Style

Both arrow and fun produce the same results but with different syntax:

  • arrow: Pipeline style -- x % arrow(f) ^ arrow(g) (data flows left-to-right)
  • fun: Mathematical style -- fun(f) * fun(g) % x (compose right-to-left, apply at end)

So how should we use it differently?
Actually, Haskell-Style is not in vogue in languages other than Haskell.
So, πŸ“ "basically", we recommend Pipeline-Style πŸ“, which is popular in many languages.

However, Haskell-Style is still really useful.
For example, Point-Free-Style.

See below for more information on Point-Free-Style:

But when it comes down to it, ✨choose whichever you want to write✨.
luarrow aims to make your programming entertaining!

πŸ“¦ Installation

With luarocks

$ luarocks install luarrow

Check that it is installed correctly:

$ eval $(luarocks path) && lua -e "local l = require('luarrow'); print('Installed correctly!')"

With Git

$ git clone https://github.com/aiya000/luarrow.lua?target=https://github.com
$ cd luarrow.lua
$ make install-to-local

With Neovim

For Neovim users, you can install luarrow using your preferred package manager:

lazy.nvim

{
  'aiya000/luarrow.lua',
  build = 'luarocks install --lua-version 5.1 luarrow',
}

packer.nvim

use {
  'aiya000/luarrow.lua',
  run = 'luarocks install --lua-version 5.1 luarrow',
}

vim-plug

Plug 'aiya000/luarrow.lua', { 'do': 'luarocks install --lua-version 5.1 luarrow' }

For package manager installations (lazy.nvim, packer.nvim, vim-plug), you need to ensure Neovim can find the LuaRocks modules. Add this to your init.lua before requiring luarrow:

  1. Create ~/.config/nvim/lua/luarocks.lua

Tip

This is useful common function for luarocks packages. You can also use it for other packages.

-- Ensure LuaRocks is installed and available in PATH
if vim.fn.executable('luarocks') ~= 1 then
  error('LuaRocks is not found. Please make sure it is in your PATH.')
end

-- Add LuaRocks paths to Neovim's package.path and package.cpath
-- Note: Explicitly request Lua 5.1 (Neovim's LuaJIT version) paths from LuaRocks
local function add_luarocks_paths()
  local handle, popen_err = io.popen('luarocks path --lua-version 5.1')
  if not handle then
    error('Failed to run "luarocks path": ' .. (popen_err or 'unknown error'))
  end

  local result = handle:read('*a') or ''
  handle:close()

  local function trim_trailing_separators(s)
    return (s:gsub('[;%s]+$', ''))
  end

  -- Extract LUA_PATH from the shell commands printed by `luarocks path`
  local lua_path = result:match('LUA_PATH%s*=%s*"([^"]+)"')
                or result:match("LUA_PATH%s*=%s*'([^']+)'")
                or result:match('LUA_PATH%s*=%s*([^\n]+)')
  if lua_path and lua_path ~= '' then
    lua_path = trim_trailing_separators(lua_path)
    if lua_path ~= '' then
      package.path = package.path .. ';' .. lua_path
    end
  end

  -- Extract LUA_CPATH from the shell commands printed by `luarocks path`
  local lua_cpath = result:match('LUA_CPATH%s*=%s*"([^"]+)"')
                 or result:match("LUA_CPATH%s*=%s*'([^']+)'")
                 or result:match('LUA_CPATH%s*=%s*([^\n]+)')
  if lua_cpath and lua_cpath ~= '' then
    lua_cpath = trim_trailing_separators(lua_cpath)
    if lua_cpath ~= '' then
      package.cpath = package.cpath .. ';' .. lua_cpath
    end
  end
end

add_luarocks_paths()
  1. Add following line to your init.lua to load the luarocks config:
require('luarocks')
  1. (optional) Verify that luarrow is now properly usable

On your Neovim:

:lua = require('luarrow')
" {
"   arrow = <function 1>,
"   fun = <function 2>
" }

" (And other modules.)
  1. Use require('luarrow') in your Neovim Lua code to access luarrow's API.
local arrow = require('luarrow').arrow
local fun = require('luarrow').fun

Note

Order of require() for this case is important. First, require('plugins') and require('luarocks') to load luarrow. (assuming ~/.config/nvim/lua/plugins.lua manages your plugins including luarrow as above mentioned lines.) Next, require('luarrow').


Note

If you encounter issues with require('luarrow'), ensure that:

  1. LuaRocks is installed and configured for Lua 5.1 (check with luarocks show luarrow after installation)
  2. The package was installed with the correct Lua version: luarocks install --lua-version 5.1 luarrow
  3. Alternatively, launch Neovim with LuaRocks paths pre-configured:
    • Unix/macOS: eval $(luarocks path --lua-version 5.1) && nvim
    • Windows (PowerShell): Run luarocks path --lua-version 5.1 and apply the printed LUA_PATH / LUA_CPATH values in your PowerShell session before starting nvim

Manually (git clone or git submodule)

You can also add luarrow directly to your Neovim config directory without a package manager.

With git clone:

$ git clone https://github.com/aiya000/luarrow.lua?target=https://github.com ~/.config/nvim/lua/luarrow

With git submodule (if your Neovim config is a git repository):

$ cd ~/.config/nvim
$ git submodule add https://github.com/aiya000/luarrow.lua?target=https://github.com lua/luarrow

Then add the src directory to the Lua path in your init.lua:

-- Add luarrow's src directory to the Lua path
local luarrow_src = vim.fn.stdpath('config') .. '/lua/luarrow/src'
package.path = luarrow_src .. '/?.lua;' .. package.path

-- Now you can use luarrow!
local arrow = require('luarrow').arrow
local fun = require('luarrow').fun

πŸ“š API Reference

For complete API documentation, see ./doc/api.md.

For practical examples and use cases, see ./doc/examples.md.

Quick reference for fun:

  • fun(f) -- Wrap a function for composition
  • f * g -- Compose two functions in mathematical order (f ∘ g)
  • f % x -- Apply function to value in Haskell-Style

Quick reference for arrow:

  • arrow(f) -- Wrap a function for pipeline
  • f ^ g -- Compose two functions in pipeline order (f |> g)
  • x % f -- Apply function to value in Pipeline-Style

Quick reference for let:

  • let(x, y, ...) -- Hold multiple values as a pipeline entry point
  • let(x, y) % arrow(f) -- Start an arrow pipeline with multiple values
  • fun(f) % let(x, y) -- Start a fun pipeline with multiple values

Quick reference for luarrow.utils.list:

  • list.map(f) -- Apply f to each element
  • list.filter(pred) -- Keep elements satisfying pred
  • list.foldl(f, init) -- Left fold with initial value
  • list.find(pred) -- First element satisfying pred
  • list.sort_by(key) -- Sort by key function
  • list.sort_with(cmp) -- Sort with comparator function
  • list.group_by(f) -- Group elements by key function
  • ...and many more

πŸ”„ Comparison Haskell-Style with Real Haskell

Haskell luarrow Pure Lua
let k = f . g local k = fun(f) * fun(g) local function k(x) return f(g(x)) end
f . g . h $ x fun(f) * fun(g) * fun(h) % x f(g(h(x)))

The syntax is remarkably close to Haskell's elegance, while staying within Lua's operator overloading capabilities!

πŸ”„ Comparison Pipeline-Style with PHP

PHP luarrow Pure Lua
$x |> $f |> $g |> var_dump x % arrow(f) ^ arrow(g) ^ arrow(print) print(g(f(x)))

The syntax is remarkably close to general language's elegant pipeline operator, too!

Note

PHP's pipeline operator is shown as a familiar comparison example. Currently, this PHP syntax is at the RFC stage.

πŸ’‘ Real-World Examples

Data Transformation Pipeline (fun)

local fun = require('luarrow').fun

local trim = function(s) return s:match("^%s*(.-)%s*$") end
local uppercase = function(s) return s:upper() end
local add_prefix = function(s) return "USER: " .. s end

local process_username = fun(add_prefix) * fun(uppercase) * fun(trim)

local username = process_username % "  alice  "
print(username)  -- "USER: ALICE"

Important

This definition style for process_username is what Haskell programmers call 'Point-Free Style'!
In Haskell, this is a very common technique to reduce the amount of code and improve readability.

Numerical Computations (arrow)

local arrow = require('luarrow').arrow

local _ = 5
  % arrow(function(x) return -x end)
  ^ arrow(function(x) return x + 10 end)
  ^ arrow(function(x) return x * x end)
  ^ arrow(print)  -- 25

List Processing (luarrow.utils.list)

local arrow = require('luarrow').arrow
local list = require('luarrow.utils.list')

-- Curried list functions compose directly with arrow!
local _ = { 1, 2, 3 }
  % arrow(list.map(function(x) return x + 10 end))   -- { 11, 12, 13 }
  ^ arrow(list.filter(function(x) return x % 2 ~= 0 end))  -- { 11, 13 }
  ^ arrow(list.find(function(x) return x > 10 end))  -- 11
  ^ arrow(print)

Multi-Value Composition (arrow and fun)

Both arrow and fun naturally support functions that pass multiple values through a pipeline:

local arrow = require('luarrow').arrow

local function split_name(full_name)
  local first, last = full_name:match('(%S+)%s+(%S+)')
  return first, last
end

local function create_email(first, last)
  return string.format('%s.%s@company.com', first:lower(), last:lower())
end

local email = 'John Doe' % arrow(split_name) ^ arrow(create_email)
print(email)  -- john.doe@company.com

When you need to start a pipeline with multiple values, use let(x, y, ...) instead of :apply():

local arrow = require('luarrow').arrow
local let = require('luarrow').let

local _ = let(10, 20)
  % arrow(function(x, y) return x * 10, y * 20 end)
  ^ arrow(function(x, y) return tostring(x + y) end)
  ^ arrow(print)  -- "500"

πŸ“– Documentation

πŸ™ Acknowledgments

Inspired by Haskell's elegant function composition and the power of operator overloading in Lua.

πŸ’­ Philosophy

"The best code is code that reads like poetry."

luarrow brings functional programming elegance to Lua, making your code more expressive, composable, and maintainable. Whether you're building data pipelines, processing lists, or creating complex transformations, luarrow makes your intent crystal clear.


Like this project?
Give it a ⭐ to show your support!

Happy programming! 🎯


Footnotes

  1. To be precise, a pipeline operator RFC has been submitted for PHP 8.5. Reference ↩

  2. In Lua, expressions cannot stand alone at the top level - they must be part of a statement. The local _ = assigns the result to an unused variable (indicated by _, a common convention), allowing the pipeline expression to be valid Lua syntax. ↩

  3. Are you a new comer for the pipeline operator? Alright! The pipeline operator is a very simple idea. For easy understanding, you can find a lot of documentations if you google it. Or for the detail, my recommended documentation is 'PHP RFC: Pipe operator v3'. ↩

About

True pipeline operators and elegant Haskell-style function composition for Lua

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages