Luarrow – 真正的管道操作符和优雅的 Haskell 风格函数组合。
Luarrow – True pipeline operators and elegant Haskell-style function compositio

原始链接: https://github.com/aiya000/luarrow.lua

## luarrow:Lua 中的优雅函数管道 luarrow 为 Lua 带来了受 Haskell 启发的函数组合和真正的管道操作符,增强了代码的可读性和可维护性。它利用 Lua 的运算符重载,提供了两种风格:**管道风格**(使用 `%` 和 `^` 实现从左到右的数据流,类似于 OCaml/Julia)和 **Haskell 风格**(使用 `*` 和 `%` 实现从右到左的组合)。 该库提供了 `arrow` 用于管道组合(例如 `42 % arrow(f) ^ arrow(g)`)和 `fun` 用于数学风格的组合(例如 `fun(f) * fun(g) % x`)。两者都能达到相同的结果,但满足不同的偏好——管道风格因其更广泛的应用而推荐。 luarrow 是纯 Lua 实现,没有依赖项,提供出色的性能,尤其是在 LuaJIT 环境中。它允许编写更简洁、更具表现力的代码,从而实现诸如“无点风格”之类的技术。可以通过 [luarocks](https://luarocks.org/) 轻松安装。详细文档和示例可在 [GitHub](https://github.com/aiya000/luarrow.lua) 上找到。

一个新的 Lua 库“Luarrow”旨在为该语言带来真正的管道操作符和 Haskell 风格的函数组合。该库使用 `%` 和 `^` 符号将函数串联起来,从而创建可读的数据转换流程。 然而,Hacker News 上的讨论显示出一些担忧。用户指出,`^` 操作符垂直堆叠时视觉上显得笨拙,感觉指向“错误”的方向。一个关键的批评集中在“arrow”(箭头)这个关键字的选择上,有人建议使用“which”(哪个)或“through”(通过)等替代词可以提高可读性和易用性。 一些评论者建议如果喜欢这种风格,可以直接使用 Haskell,而另一些人指出 Lua 现有的函数语法可以变得更简洁,从而更好地支持这种模式。总的来说,该库因其雄心壮志而受到赞扬,但同时也引发了关于语法选择以及 Lua 是否适合这种函数式编程风格的争论。
相关文章

原文

|> The true Pipeline-operator |>

.$ The Haskell-inspired function compositions .$

*% The new syntax for Lua, and you ^%

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 ./luarrow.lua/doc/ directory.

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

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!

$ luarocks install luarrow

Check that it is installed correctly:

$ eval $(luarocks path) && lua -e "local l = require('luarrow'); print('Installed correctly!')"
$ git clone https://github.com/aiya000/luarrow.lua
$ cd luarrow.lua
$ make install-to-local

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

For practical examples and use cases, see luarrow.lua/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

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

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
local fun = require('luarrow').fun

local map = function(f)
  return function(list)
    local result = {}
    for i, v in ipairs(list) do
      result[i] = f(v)
    end
    return result
  end
end

local filter = function(predicate)
  return function(list)
    local result = {}
    for _, v in ipairs(list) do
      if predicate(v) then
        table.insert(result, v)
      end
    end
    return result
  end
end

local numbers = {1, 2, 3, 4, 5, 6}

local is_even = function(x) return x % 2 == 0 end
local double = function(x) return x * 2 end

local result = fun(map(double)) * fun(filter(is_even)) % numbers
print(result) -- { 4, 8, 12 }

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

"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! 🎯


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

联系我们 contact @ memedata.com