{"id":13896128,"url":"https://github.com/wqferr/functional","last_synced_at":"2026-01-23T14:34:07.899Z","repository":{"id":46955220,"uuid":"166049459","full_name":"wqferr/functional","owner":"wqferr","description":"Functional programming utilities implemented in pure Lua.","archived":false,"fork":false,"pushed_at":"2024-10-30T19:50:17.000Z","size":332,"stargazers_count":12,"open_issues_count":8,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-25T01:33:31.109Z","etag":null,"topics":["functional-programming","lua","lua-library","luarocks","teal"],"latest_commit_sha":null,"homepage":"https://wqferr.github.io/functional/","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wqferr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-01-16T13:59:53.000Z","updated_at":"2024-10-30T19:50:20.000Z","dependencies_parsed_at":"2024-11-25T01:31:23.243Z","dependency_job_id":"985b9457-47db-4a9a-baa7-ed204678aefc","html_url":"https://github.com/wqferr/functional","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/wqferr/functional","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wqferr%2Ffunctional","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wqferr%2Ffunctional/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wqferr%2Ffunctional/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wqferr%2Ffunctional/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wqferr","download_url":"https://codeload.github.com/wqferr/functional/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wqferr%2Ffunctional/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28694347,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T14:15:13.573Z","status":"ssl_error","status_checked_at":"2026-01-23T14:09:05.534Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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","lua-library","luarocks","teal"],"created_at":"2024-08-06T18:02:40.769Z","updated_at":"2026-01-23T14:34:07.880Z","avatar_url":"https://github.com/wqferr.png","language":"Lua","readme":"# functional\nFunctional programming utilities implemented in pure Lua.\n\n\u003e The motivation behind this module is, again, portability.\nIf you want to embed this code on a webpage, or use it in some weird\nsystem for which a C binding wouldn't work, this project is aimed\nat you.\n\n[The docs can be found here](https://wqferr.github.io/functional/), and were\ngenerated by [LDoc](https://github.com/stevedonovan/LDoc). This module is\npublished as a rock at [luarocks.org/modules/wqferr/functional](http://luarocks.org/modules/wqferr/functional).\n\n# About\nThis module seeks to provide some utility functions and structures\nwhich are too verbose in vanilla Lua, in particular with regards to iteration\nand inline function definition.\n\nThe module is writen completely in vanilla Lua,\nwith no dependencies on external packages. This was a decision made for\nportability, and has drawbacks. Since none of this was written as a C binding, it is not\nas performant as it could be.\n\nFor example, [luafun](https://github.com/luafun/luafun)\nis \"high-performance functional programming library for Lua designed with\n[LuaJIT](http://luajit.org/luajit.html)'s trace compiler in mind\".\nIf your environment allows you to use LuaJIT and performance is a\nconcern, perhaps luafun will be more suited for your needs.\n\n**The motivation behind this module is, again, portability.\nIf you want to embed this code on a webpage, or use it in some weird\nsystem for which a C binding wouldn't work, this project is aimed\nat you.**\n\n## Teal\nThis project also includes `functional.d.tl`, which allows it to be used with\n[Teal](https://github.com/teal-language/tl): a typed dialect of Lua. The Makefile\nincluded is a workaround so LuaRocks actually installs the `.d.tl` file as well.\n\nTeal support is not complete, however, since some functions require features not\nyet stable on the Teal compiler side. For more information on how to use this\nlibrary with Teal, see \"Usage with Teal\" below.\n\n# Learning the Ropes\n\nIf this is your first time using (or even seeing) these kinds of \"stream manipulating\noperators\" and all those other fancy words, don't worry: we'll start from the beginning.\n\n## Iterators\n\nSometimes called \"streams\", they are just that: some... *thing* that can produce values\nover time through iteration.\n\nLet's say we want to get an array with the numbers from 1 to 10. Sounds easy?\n\n```lua\nlocal f = require \"functional\"\n\nlocal my_counter = f.counter()\nlocal my_capped_counter = my_counter:take(10)\nlocal my_array = my_capped_counter:to_array()\n\nprint(type(my_array))\nfor i, v in ipairs(my_array) do\n  print(i, v)\nend\n```\n\nThat may seem like a lot, but those three lines make sense if you think of each step\nindividually. Starting from the top down:\n\n`f.counter()` is a function that creates an iterator that just counts up. Forever.\nWell, this is a start, but we don't want an infinite array! We want to cut it short!\n\n`my_counter:take(10)` will do just that: cut the previous thing short, and stop\nafter 10 elements have gone through this step.\n\nThe `to_array` method just collects all the items in the stream into an array. That\ntransforms the abstract and scary \"iterator\" into a more familiar face.\n\nFrom that point on, it's just regular Lua: `ipairs` into printing.\n\n## Operator Chains\n\nNow here's the neat part: you don't have to assign each step to a new variable. We don't\neven use most of them except to define the next step.\n\nInstead, we can just collapse them all, as below:\n\n```lua\nlocal my_array = f.counter()     -- my_counter\n                    :take(10)    -- my_capped_counter\n                    :to_array()  -- my_array\nfor i, v in ipairs(my_array) do\n  print(i, v)\nend\n```\n\nAnd of course, the line breaks between each step are optional: I just put them there for clarity.\nIn other words, you can create that 1-10 array in a single line!\n\nWhat? You could've just typed `{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}`, yeah, but what about getting\nan array with all numbers from 1 to 1000? You'd have to write a for loop, and for loops in Lua\nare verbose.\n\nAnd this is the bare-bones example: how about we try something a little more interesting?\n\n## Filtering\n\nGiven a list of names, which ones contain the letter B? Let's assume you already have this\nchunk of code:\n\n```lua\nlocal names = {\n  \"Arya\",\n  \"Beatrice\",\n  \"Caleb\",\n  \"Dennis\"\n}\n\nlocal function has_b(name)\n  if name:find(\"[Bb]\") then\n    return true\n  else\n    return false\n  end\nend\n```\n\nIn pure Lua, you could write something like:\n```lua\nlocal names_with_b = {}\nfor _, name in ipairs(names) do\n  if has_b(name) then\n    table.insert(names_with_b, name)\n  end\nend\n```\n\nHere, `has_b` is called a predicate: a simple function which returns `true` or `false` for any given name.\nPredicates are especially good for filtering. Either you keep something, or you throw it out. In fact, the\nwhole loop is a really common pattern: `if predicate(val): keep(val)`. `functional` has the function\n`filter` and the operator `:filter` to do just that:\n\n```lua\nlocal names_with_b = f.filter(names, has_b):to_array()\n```\n\n## Mapping\n\nNow, what if you wanted all these names in all caps? In pure Lua, you'd have to change\nthe core loop:\n\n```lua\nlocal names_with_b = {}\nfor _, name in ipairs(names) do\n  if has_b(name) then\n    table.insert(names_with_b, string.upper(name))\n  end\nend\n```\n\nIn a functional environment, you can just add another operator. Since we're applying the same\nfunction to all elements in a stream, we can use the `:map` operator. It transforms (or \"maps\")\nevery element in the stream to the return value of a function.\n\n```lua\nlocal names_with_b = f.filter(names, has_b)\n  :map(string.upper)\n  :to_array()\n```\n\nNow with line breaks for readability.\n\n## TODO Reducing\n\n## Lambdas\n### What are lambdas?\n\nIf you've never heard of lambdas, you might be thinking this is some sort of \"ligma\" joke. It isn't.\n\nMany languages (including Lua!) have a way to declare anonymous functions. That is, a function\nthat is declared \"on the spot\", just to be used as an argument to another function, or to be\nstored in a variable. What most of these languages have that Lua lacks is a shorthand notation\nfor creating such functions.\n\nIn Lua, there's no getting around typing `function()` (plus parameters) and `end`. That's 13\ncharacters typed minimum for the simplest anonymous functions. As an example, here's the definition\nof a function that doubles its argument:\n\n```lua\ntriple = function(x) return 3*x end\n```\n\nCompare that to Python:\n\n```python\ntriple = lambda x: 3*x\n```\n\nThat's nearly half the characters, and the Lua example doesn't even declare `triple` as a local.\nAnd JavaScript's is even shorter!\n\n```javascript\ntriple = (x) =\u003e 3*x\n```\n\n### OK, but why does it matter?\n\nIt's perfectly fine to use vanilla Lua's notation to declare anonymous functions, and for anything\nmore complex than a single operation, you **should** create a proper Lua function instead of using\nthis library's lambdas. But for extremely short functions, it's nice to have a shortcut, both for\ncoding quick and dirty examples and for readability.\n\n### Defining lambdas\n\nThe way you declare a lambda with functional is with a simple function call:\n\n```lua\ntriple = f.lambda \"(x) =\u003e 3*x\"\n```\n\nThis syntax is quite similar to the Javascript one, where the parameter names are given between\nparenthesis and there is an arrow separating the parameters from the body. The syntax also allows\nfor higher order lambdas easily by chaining `()=\u003e` sections:\n\n```lua\nlinear_combinator = f.lambda \"(m) =\u003e (x, y) =\u003e m*x + y\"\ncomb2 = linear_combinator(2)\nprint(comb2(3, 4)) --\u003e 10\n```\n\n### Security concerns and limitations\n\nUnder the hood, `f.lambda` uses `load()` (or `loadstring()` in older versions of Lua).\n\n**That means creating lambdas from unknown strings is a security risk.**\n\nIf you're hardcoding all lambdas, it *should* be fine. There are some checks done internally\nbefore `load()` takes place, to prevent simple errors or naïve attacks. You can check the\ndocumentation proper for the exact restrictions, but you shouldn't run into any of them with\nsimple functions.\n\n### Environments\n\nBy default, a lambda function's environment is set to an empty table. That means it only has\naccess to its own parameters, and cannot read or write to globals or variables in the enclosing\nscope of its creation. For example, the following lambda:\n\n```lua\nlocal constant = 3.14\nlocal l = f.lambda \"() =\u003e constant\"\nprint(l()) --\u003e nil\n```\n\nWill print out `nil`: `constant` is an undefined variable from the lambda's point of view, so\nits value is `nil` since it was never assigned.\n\nYou can get around this and set the desired environment for a lambda as follows:\n\n```lua\nlocal constant = 3.14\nlocal l = f.lambda(\"() =\u003e 3*con\", {con=constant})\nprint(l()) --\u003e 9.42\n```\n\nOr, as a shorthand:\n\n```lua\nlocal constant = 3.14\nlocal l = f.lambda{\"() =\u003e 3*con\", con = constant}\nprint(l()) --\u003e 9.42\n```\n\nOf course you could use (almost) any name you wish for the lambda environment variables.\nIn this case, we chose to distinguish the names for `con` and `constant` for the sake of clarity\nfor what is and isn't in the lambda scope.\n\n## You don't need `:to_array()` (probably)\nIn most cases, unless you specifically need an array, you can use the iterator itself in a for\nloop. For example, if we wanted to print all the names with \"b\", we could write:\n\n```lua\nlocal names_with_b = f.filter(names, has_b)  -- note the lack of :to_array()\nfor name in names_with_b do\n  print(name)\nend\n```\n\nThis is preferred for a couple reasons: (1) it removes the need to allocate a new table which\nwould be later discarded; and (2) it postpones processing to when it actually needs to happen.\n\nThe second point is due to iterators being lazy, in the technical sense. They don't actually go\nthrough their input and save whichever values they should return. Instead, whenever they're asked\nfor the next value, they process it and return it.\n\nThis also means, however, that an iterator can't rewind. If you need to iterate through the same\nvalues twice, then maybe `:to_array()` is indeed the solution. If the sequence is too large\nto be put into an array, you could instead `:clone()` the iterator. It will make a snapshot\nof the iterator and all its dependencies, and return it as a new, independent iterator.\n\nYet another alternative is using the `:foreach()` operator: it applies the given function to\nall elements in a sequence. Rewriting the above example using `:foreach()`:\n\n```lua\nlocal names_with_b = f.filter(names, has_b)\nnames_with_b:foreach(print)\n```\n\nOr simply:\n\n```lua\nf.filter(names, has_b):foreach(print)\n```\n\n## Examples\n### Filter\n\nPrint all even numbers up to 10:\n```lua\nlocal is_even = function(v) return v % 2 == 0 end\nf.range(1, 10)      -- run through all the numbers from 1 to 10 (inclusive)\n  :filter(is_even)  -- take only even numbers\n  :foreach(print)   -- run print for every value individually\n```\n\n### Map\n\nFix capitalization on names:\n```lua\nlocal names = {\"hellen\", \"oDYSseuS\", \"aChIlLeS\", \"PATROCLUS\"}\nlocal function fix_case(name)\n  return name:sub(1, 1):upper() .. name:sub(2):lower()\nend\n\nfor name in f.map(names, fix_case) do\n  print(\"Fixed: \", name)\nend\n```\n\n### Reduce\n\nSum all numbers in a range:\n```lua\nlocal add(acc, new)\n  return acc + new\nend\n\nlocal numbers = f.range(10, 120)\nlocal sum = numbers:reduce(add, 0)\nprint(sum)\n```\n\n### Lambdas\n#### Keep even numbers\n\n```lua\nlocal numbers = {2, 1, 3, 4, 7, 11, 18, 29}\nlocal is_even = f.lambda \"v % 2 == 0\"\nlocal even_numbers = f.filter(numbers, is_even):to_array()\n```\n\nOr you could inline the lambda, which is the more common approach:\n\n```lua\nlocal numbers = {2, 1, 3, 4, 7, 11, 18, 29}\nlocal even_numbers = f.filter(numbers, f.lambda \"v % 2 == 0\"):to_array()\n```\n\n#### Get first element of each row\n\n```lua\nlocal matrix = {\n  {1, 2, 3}, -- first element of matrix\n  {4, 5, 6}, -- second element of matrix\n  {7, 8, 9}  -- third element of matrix\n}\n\n-- map will iterate through each row, and the lambda\n-- indexes each to retrieve the first element\nlocal vals = f.map(matrix, f.lambda \"v[1]\"):to_array()\n```\n\n# Usage with Teal\n**It is recommended that you read the Learning the Ropes section first so\nyou're not completely lost here.**\n\nDue to the nature of Lua, Teal's types can be a bit too strict at times.\nThat's why Teal has casts. However, when you're dealing with functions,\ncasts can be quite verbose, and this is something the library is meant\nto remedy!\n\nFor that, there are 3 aliases for common function types:\nAlias|Explicit Teal type\n:---:|:----------------:\n`consumer\u003cT\u003e`|`function(T)`\n`producer\u003cT\u003e`|`function(): T`\n`mapping\u003cU, V\u003e`|`function(U): V`\n\nShort story long: consumers take in a value and return nothing; producers\ntake in nothing and produce a value; and mappings take a value and transform\nit into another.\n\n## A practical example\n\nLet's say you want to print every third number up to 20, except if it's prime.\n\n```lua\nlocal is_prime = function(n: integer): boolean ... end\nlocal my_numbers = f.range(20)\n  :every(3)\n  :filter(f.negate(is_prime))\n```\n\nSo far, so good! `is_prime` is a `mapping` from integer to boolean, so negate\nproperly propagates the types and filter accepts it, since `range` produces\nintegers.\n\nHowever, when we try to print the numbers:\n\n```lua\nmy_numbers:foreach(print)\n```\n\nA type error! `print` is a consumer of `any`, but `foreach` expected a consumer\nof integers! A simple cast will suffice though:\n\n```lua\nmy_numbers:foreach(print as f.consumer\u003cinteger\u003e)\n```\n","funding_links":[],"categories":["Lua"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwqferr%2Ffunctional","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwqferr%2Ffunctional","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwqferr%2Ffunctional/lists"}