{"id":13896472,"url":"https://github.com/siffiejoe/lua-fx","last_synced_at":"2025-12-27T15:03:54.912Z","repository":{"id":147352771,"uuid":"58851089","full_name":"siffiejoe/lua-fx","owner":"siffiejoe","description":"Functional Experiments in Lua","archived":false,"fork":false,"pushed_at":"2020-07-12T06:36:05.000Z","size":89,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-25T02:32:07.473Z","etag":null,"topics":["functional-programming","lua","transducers"],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/siffiejoe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2016-05-15T08:20:20.000Z","updated_at":"2023-05-07T08:39:33.000Z","dependencies_parsed_at":"2023-07-02T19:31:17.135Z","dependency_job_id":null,"html_url":"https://github.com/siffiejoe/lua-fx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/siffiejoe/lua-fx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siffiejoe%2Flua-fx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siffiejoe%2Flua-fx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siffiejoe%2Flua-fx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siffiejoe%2Flua-fx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/siffiejoe","download_url":"https://codeload.github.com/siffiejoe/lua-fx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/siffiejoe%2Flua-fx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28080200,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-27T02:00:05.897Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","lua","transducers"],"created_at":"2024-08-06T18:02:56.660Z","updated_at":"2025-12-27T15:03:54.857Z","avatar_url":"https://github.com/siffiejoe.png","language":"Lua","funding_links":[],"categories":["Lua"],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/siffiejoe/lua-fx.svg?branch=master)](https://travis-ci.org/siffiejoe/lua-fx)\n\n#                 FX -- Functional Experiments in Lua                #\n\n##                           Introduction                           ##\n\nAlthough Lua can be used for functional programming, it lacks the\nusual toolset of functions that one needs for functional programming.\nSome of those functions are easily implemented in Lua, others should\nbe implemented in C to avoid unnecessary performance loss. This\nlibrary contains a small core implemented in C that aims to make\nfunctional (and in particular point-free or tacit) programming in Lua\neasier. It mimics features from Clojure, and the [Ramda][1] Javascript\nlibrary, and will at some point be retitled to \"Functional Extensions\"\nonce it leaves experimental status.\n\n  [1]: http://ramdajs.com/\n\n\n##                             Concepts                             ##\n\n###                Currying and Partial Application                ###\n\nCurrying is the process of transforming a function call taking `n`\narguments (e.g. `f( 1, 2, 3 )` into `n` function calls taking one\nargument each (`f( 1 )( 2 )( 3 )`). Every function call except the\nlast just returns a new function that is a partially applied version\nof the original one (i.e. the arguments provided so far are saved for\nthe final call). Currying and partial application are useful tools for\nfunctional programming, because they provide an easy and syntactically\npleasing way to create specialized functions from generic ones.\n\n\n###                           Transducers                          ###\n\n[*Transducers*][2] (or *reducing function transformers*) are built on\nthe realization that `reduce` is the ultimate iteration function. It\nworks by calling a *reducing function* on each tuple/value during an\niteration, returning an updated state value that is then passed to the\nnext call of the reducing function and finally returned as the result\nof `reduce`:\n\n```lua\nfunction aReducer( state, ... )\n  -- ...\n  return newState\nend\n```\n\nA transducer is a function that takes a reducing function and returns\nanother reducing function:\n\n```lua\nfunction aTransducer( aReducer )\n  return function( state, ... )\n    if predicate( ... ) then\n      return aReducer( state, f( ... ) )  -- pass (modified) data\n    else\n      return state  -- don't forward data\n    end\n  end\nend\n```\n\nThe new reducing function may forward the current iteration variables\n(modified or as-is) to the old reducing function, or it may instead\nreturn the state from the last call, basically ignoring the current\niteration step. The big advantage is that transducers only deal with\nother reducing functions, and only the very last reducing function in\nthe chain needs to be aware of the contents of the state or where the\ndata is supposed to go. Thus, you can compose complex transformations\nthat are independent of the final output, and without intermediate\ntemporary copies of the iterated data structure.\n\n  [2]: http://blog.cognitect.com/blog/2014/8/6/transducers-are-coming\n\n\n###                            Sequences                           ###\n\nWhenever this README mentions that a function operates on a sequence,\nthe usual Lua definition is assumed, i.e. a table for which the length\noperator `#` is defined. However, if the table has a positive numeric\n`.n` field (like in the table returned by `table.pack()`), that value\ntakes precedence, and the table may contain holes. Resulting tables\nalways have an `.n` field set.\n\n\n###                 To-Be-Closed Values in Lua 5.4                 ###\n\nThis library supports the the new for-loop protocol in Lua 5.4 with\nthe to-be-closed value on a best effort basis. Functions taking and\nreturning iterator triplets (quadruplets) might throw errors (in\nparticular memory and invalid argument errors) before the to-be-closed\nvalue reaches the for-loop. In cases where this is not acceptable,\nspecial steps have to be taken:\n```lua\ndo\n  local f, s, var, c \u003cclose\u003e = io.lines(\"input.txt\")\n  for line in fx.filter(\"v =\u003e #v \u003e 0\", f, s, var) do\n    -- do something\n  end\nend\n```\ninstead of the more idiomatic (and shorter, and version agnostic)\n```lua\nfor line in fx.filter(\"v =\u003e #v \u003e 0\", io.lines(\"input.txt\")) do\n  -- do something\nend\n```\n\n\n##                             Reference                            ##\n\n*   `fx.has( s ) ==\u003e f`\n\n    The `has` function takes a string of comma-separated field names\n    and returns a function that checks for the existence of those\n    fields in the given argument. The function returns `true` if all\n    required fields are non-`nil`, and `false` otherwise. If a field\n    name starts with a double underscore `\"__\"`, the field is looked\n    up in the metatable instead. Some metamethods are assumed to be\n    defined for certain Lua types (e.g. `__call` for functions).\n\n    Field names may consist only of letters, digits, and `_`.\n    Everything else (not just comma) is considered a field separator.\n    This is an extended and optimized version of a [proposal][3] on\n    the lua-l mailing list.\n\n    Example:\n    ```lua\n    assert( fx.has\"__index,__newindex\"( t ) )\n    -- raises an error if `t` is either not a table or doesn't\n    -- have a metatable with __index and __newindex defined.\n    assert( fx.has\"__add,__sub\"( n ) )\n    -- works for numbers and objects with __add and __sub.\n    ```\n\n\n*   `fx.curry( n, f ) ==\u003e f2`\n\n    `fx.curry` creates a function that supports partial application\n    simply by calling it with less than the expected number of\n    arguments `n`. The result of such a partial application supports\n    partial application the same way until all required arguments have\n    been supplied, at which point the original function `f` is called\n    with all collected argument values. The special `fx._` value can\n    be used as an argument to reserve the slot for a later call.\n    Reserved slots are filled from left to right, and the original\n    function will not be called as long as there are placeholder\n    values in the argument list.\n\n    Example:\n    ```lua\n    local f = fx.curry( 3, print )\n    -- the following expressions are all equivalent:\n    f( 1, 2, 3 )\n    f( 1 )( 2 )( 3 )\n    f( 1, 2 )( 3 )\n    f( 1 )( 2, 3 )\n    f( fx._, 2 )( 1, 3 )\n    f( fx._, 2 )( fx._, 3 )( 1 )\n    ```\n\n\n*   `fx._`\n\n    `fx._` is a placeholder value that can be used to reserve a slot\n    in the argument list during partial application, or to terminate a\n    reduction early.\n\n\n*   `fx.compose( f, ... ) ==\u003e f2`\n\n    `fx.compose` does function composition on a variable number of\n    given functions. The resulting closure calls the functions from\n    right to left, passing the return values as arguments to the next\n    function.\n\n    Example:\n    ```lua\n    local f = fx.compose( g, h, i )\n    -- is equivalent to:\n    local function f( ... )\n      return g( h( i( ... ) ) )\n    end\n    ```\n\n\n*   `fx.map( fun, t, ... ) ==\u003e t2`\n\n    `fx.map( fun, f ) ==\u003e g`  (`f` and `g` are reducing functions)\n\n    `fx.map` applies a function to all elements in a given sequence,\n    returning a new table containing the results. Extra arguments\n    passed to `fx.map` are passed as extra arguments to every call of\n    `fun` to help avoid unnecessary closures. If the second argument\n    to `fx.map` is a (reducing) function, a new reducing function is\n    returned instead (thus, the partially applied `fx.map( fun )` acts\n    as a transducer). The transducer is stateless unless `fun`\n    maintains its own state.\n\n    If `f`/`t` is not a function but defines a `__map@fx` metamethod\n    in its metatable, `fx.map` delegates to this metamethod, passing\n    all given arguments.\n\n    The `fx.map` function is automatically curried with two expected\n    arguments.\n\n\n*   `fx.filter( pred, t, ... ) ==\u003e t2`\n\n    `fx.filter( pred, f ) ==\u003e g`  (`f` and `g` are reducing functions)\n\n    `fx.filter` applies a predicate `pred` to all elements in a given\n    sequence and returns a new table containing all values for which\n    the predicate returned a `true`ish value. Extra arguments passed\n    to `fx.filter` are passed as extra arguments to every call of\n    `pred` to help avoid unnecessary closures. If the second argument\n    to `fx.filter` is a (reducing) function, a new reducing function\n    is returned instead (the partially applied `fx.filter( pred )`\n    acts as a transducer). The transducer is stateless unless `pred`\n    maintains its own state.\n\n    The `fx.filter` function is automatically curried with two\n    expected arguments.\n\n\n*   `fx.take( np, t, ... ) ==\u003e t2`\n\n    `fx.take( np, f ) ==\u003e g`  (`f` and `g` are reducing functions)\n\n    `fx.take` copies elements from the beginning of a sequence to a\n    new table. The first paramater `np` may either be a number of\n    elements to copy, or a predicate deciding when to stop copying\n    (when the predicate returns a `false`y value for the first time).\n    Extra arguments passed to `fx.take` are passed as extra arguments\n    to every call of the predicate to help avoid unnecessary closures.\n    If the second argument to `fx.take` is a (reducing) function, a\n    new reducing function is returned instead (thus, the partially\n    applied `fx.take( np )` acts as a transducer). The transducer is\n    stateful, and the internal state is initialized when the\n    transducer is called with the reducing function.\n\n    The `fx.take` function is automatically curried with two expected\n    arguments.\n\n\n*   `fx.drop( np, t, ... ) ==\u003e t2`\n\n    `fx.drop( np, f ) ==\u003e g`  (`f` and `g` are reducing functions)\n\n    Like `fx.take` `fx.drop` also copies elements from a sequence to a\n    new table, but it skips elements at the beginning. The first\n    parameter `np` may either be a number of elements to skip, or a\n    predicate deciding when to stop skipping (when the predicate\n    returns a `false`y value for the first time). Extra arguments\n    passed to `fx.drop` are passed as extra arguments to every call of\n    the predicate to help avoid unnecessary closures. If the second\n    argument to `fx.drop` is a (reducing) function, a new reducing\n    function is returned instead (thus, the partially applied\n    `fx.drop( np )` acts as a transducer). The transducer is stateful,\n    and the internal state is initialized when the transducer is\n    called with the reducing function.\n\n    The `fx.drop` function is automatically curried with two expected\n    arguments.\n\n\n*   `fx.reduce( fun, init, f [, s [, var]] ) ==\u003e val`\n\n    `fx.reduce( fun, init, t, ... ) ==\u003e val`\n\n    The `fx.reduce` function calls the given \"reducing function\" `fun`\n    for every tuple generated by the input iterator or every value\n    from the input sequence, passing an additional `state` value as\n    first argument. On the first call `state` is the same as `init`,\n    on subsequent calls the `state` is the (first) result of the\n    previous call to the reducing function. The result of the last\n    call is returned from `fx.reduce` as `val`. If the reducing\n    function returns `fx._` as the second return value (after the new\n    `state` value), `fx.reduce` returns immediately with the new\n    `state` as `val`.\n\n    If the third argument is a function, `f`, `s`, and `var` are\n    evaluated as iterator triplet and the function `fun` is called for\n    every generated tuple `var_1, ..., var_n`, passing it after the\n    `state` value.\n\n    Otherwise the third argument is assumed to be a sequence(-like\n    object) which is indexed using consecutive integers starting from\n    `1` and ending at the value of the `n` field or the result of the\n    length operator. The function `fun` is called for every value `v`:\n    `fun( state, v, ... )`. Extra arguments to `fx.reduce` are passed\n    as additional arguments to every reducing function call.\n\n    `fx.reduce` can also be used to execute transducers, because a\n    transducer, when called with a reducing function as argument,\n    returns another reducing function.\n\n    Example:\n\n    ```lua\n    local appending = fx.curry( 2, function( n, state, ... )\n      state[ #state+1 ] = select( n, ... )\n      return state\n    end )\n    local function double( v ) return 2*v end\n    local xducer = fx.compose( fx.take( 5 ), fx.map( double ) )\n    local t2 = fx.reduce( xducer( appending( 1 ) ), {}, t1 )\n    ```\n\n    This protocol of calling the transducer with the final reducing\n    function right before passing it to `fx.reduce` is important for\n    stateful transducers, because this is the time when the internal\n    state (nothing to do with the `state` value) is initialized.\n\n    Transducers can be composed like normal functions, but they take\n    effect from left to right!\n\n  [3]: http://lua-users.org/lists/lua-l/2013-05/msg00426.html\n\n\n###                    Short Lambda Expressions                    ###\n\nIn many circumstances the functions above, and some glue functions\ndescribed below, accept a short lambda expression as a string instead\nof a real function. A short lambda expression has the following\nformat:\n```lua\n\u003carg\u003e [,\u003carg\u003e]* =\u003e [\u003cexpr\u003e [, \u003cexpr\u003e]*]\n```\nVararg lists are supported as well. The string is compiled into a Lua\nfunction on-the-fly using `lua_load()`, so the following two lines are\nroughly equivalent:\n```lua\nlocal f = fx.compose( \"x,y =\u003e x+y, x*y\" )\nlocal g = load( \"return function(x,y) return x+y, x*y end\" )()\n```\nThere is no caching/memoization going on, so be aware of the\nperformance implications if you do this in a tight loop. In fact, it\nis recommended to use the short lambdas only as part of function\ncompositions (see also the `fx.glue` module below).\n\nThe following functions accept short lambda expressions: `fx.curry`,\n`fx.compose`, `fx.map` (first argument), `fx.filter` (first argument),\n`fx.take` (first argument), `fx.drop` (first argument), `fx.reduce`\n(first argument), `fx.glue.vmap`, and `fx.glue.vtransform`.\n\n\n###                         Glue Functions                         ###\n\nUnlike many functional programming languages, Lua supports multiple\nreturn values. This makes composing functions more powerful, but it\nalso increases the chances of a mismatch between what one function\nprovides and the next one expects. Most common cases can (and should)\nbe handled with string lambdas, but for some akward situations the\n`fx.glue` module provides glue functions that transform argument or\nreturn value lists. It is similar in scope to the [`vararg`][4]\nmodule, but since it is intended to be used with `compose`, the glue\nfunctions in this module create closures that do the actual vararg\nmanipulation.\n\nThe following glue functions are provided:\n\n*   `vmap( fun [, idx1 [, idx2]] ) ==\u003e f`\n\n    Returns a glue function that applies the function `fun` to each\n    argument (between indices `idx1` and `idx2`, inclusively) and\n    returns the results (the arguments outside of the specified range\n    are passed unmodified). `fun`'s results are always adjusted to one\n    return value for each call. `vmap` accepts short lambdas.\n\n*   `vtransform( f1 [, f2 [, ..., fn]] ) ==\u003e f`\n\n    Returns a glue function that applies `f1` to `select( 1, ... )`\n    to create the first return value, `f2` to `select( 2, ... )` to\n    create the second return value, and so on. The last `fn` may\n    return multiple values as usual. `vtransform` accepts short\n    lambdas.\n\n*   `vinsert( idx, v, ... ) ==\u003e f`\n\n    Returns a glue function that inserts all values `v, ...` before\n    index `idx`. Using `nil` as `idx` will append to the vararg list.\n\n*   `vreplace( idx, v, ... ) ==\u003e f`\n\n    Returns a glue function that replaces the arguments starting at\n    index `idx` with the values `v, ...`.\n\n*   `vreverse( [idx1 [, idx2]] ) ==\u003e f`\n\n    Returns a glue function that reverses all arguments between the\n    indices `idx1` and `idx2` (inclusively). `idx1` defaults to `1`,\n    and `idx2` defaults to `-1` (the last argument).\n\n*   `vrotate( [idx [, n]] ) ==\u003e f`\n\n    Returns a glue function that right shifts all arguments starting\n    at index `idx1` by `n` positions, reinserting the values that fall\n    off at index `idx1`. `n` may be negative (to do a left shift) and\n    defaults to `1`. `idx` defaults to `1` as well. (This is basically\n    an interface to the `lua_rotate()` API function.)\n\n  [4]: https://github.com/moteus/lua-vararg\n\n\n##                           Installation                           ##\n\nCompile the C source file `fx.c` into a shared library (`fx.so`, or\n`fx.dll` on Windows) as usual for your platform and put it somewhere\ninto your Lua `package.cpath`. Copy `fx.lua` somewhere into your Lua\n`package.path`.\n\nYou can also use LuaRocks.\n\n\n##                             Contact                              ##\n\nPhilipp Janda, siffiejoe(a)gmx.net\n\nComments and feedback are always welcome.\n\n\n##                             License                              ##\n\n**FX** is *copyrighted free software* distributed under the MIT\nlicense (the same license as Lua 5.1). The full license text follows:\n\n    FX (c) 2013-2020 Philipp Janda\n\n    Permission is hereby granted, free of charge, to any person obtaining\n    a copy of this software and associated documentation files (the\n    \"Software\"), to deal in the Software without restriction, including\n    without limitation the rights to use, copy, modify, merge, publish,\n    distribute, sublicense, and/or sell copies of the Software, and to\n    permit persons to whom the Software is furnished to do so, subject to\n    the following conditions:\n\n    The above copyright notice and this permission notice shall be\n    included in all copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n    IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY\n    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsiffiejoe%2Flua-fx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsiffiejoe%2Flua-fx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsiffiejoe%2Flua-fx/lists"}