1 Introduction
This article discusses the following module that supports functional programming in Lua: luafun -- https://luafun.github.io/index.html
The luafun library enable us to chain functions together, feeding the result of the previous function into the following function. And, they each support lazy evaluation, which means that, when we process items in a collection, the chain of processing for one item completes before the chain of evaluation for the next item starts. In effect, we do not have to wait for all the items in the collection to be processed before completing the chain of evaluation. The examples given below and the order of the messages that they print out makes this clear.
2 luafun -- Lua Functional Library
The latest version of the script discussed below is here: luafun_example.zip
Here is a Lua script that uses several of the features of luafun:
#!/usr/bin/env lua -- -- Various tests for module `fun`. -- Tests for laziness. -- Test for `fun.chain`, `fun.map`, `fun.enumerate`, `fun.each`, -- `fun.reduce`, `fun.cycle`, `fun.take`, etc. -- M = {} local fun = require('fun') function M.test01(count, quiet) quiet = quiet == true or false local verbose = true local a = { 11, 22, 33, 44, } local b = { "aa", "bb", "cc", } local c = fun.range(1, 3) local calc_fn = function(x, n) local val if type(x) == 'number' then val = x * n elseif type(x) == 'string' then val = x .. '|' .. x else val = 'unknown' end return val end local make_map_fun = function(msg, n, func) local inner_fn = function(x) if not quiet then print(msg, x) end val = calc_fn(x, n) return val end return inner_fn end local f1 = make_map_fun('(f1) x:', 5) local f2 = make_map_fun('(f2) x:', 10) local f3 = make_map_fun('(f3) x:', 100) local f4 = make_map_fun('(f4) x:', 1000) local show = function(i, v) if verbose then print(string.format('result -- i: %d v: %s', i, v)) print('----') end end local acc_fn = function(acc, val) if type(val) == 'number' then acc = acc + val elseif type(val) == 'string' then acc = acc + #val end return acc end fun.chain(a, b, c) :cycle() :take(count) :map(f1) :map(f2) :map(f3) :map(f4) :enumerate() :each(show) end function M.main() quiet = false if arg[1] == '-h' or arg[1] == '--help' then print('usage: lua test21.lua [-q | --quiet] [n]') os.exit() elseif arg[1] == '-q' or arg[1] == '--quiet' then quiet = true table.remove(arg, 1) end local count = 1 if #arg == 1 then count = tonumber(arg[1]) end print('test 08') print('-------') M.test01(count, quiet) end if os.getenv('LUACLI') == nil then return M else M.main() end
Notes:
In order to run this script from the command line, use something like this:
$ LUACLI=1 lua test21.lua 10 $ LUACLI=1 lua test21.lua --quiet 10
Using an environment variable (in this case LUACLI) enables us to use this script from either the command line for from within another Lua module.
In order to run this test at the Lua interactive prompt, do this:
> module = require('test21') > module.test01(20) > module.test01(20, true)
We can modify a snippet in the above code so that it accumulates a sum of some of the values that we iterate over. We us fun.reduce (alias fun.foldl) to do the accumulation:
local sum = fun.chain(a, b, c) :cycle() :take(count) :map(f1) :map(f2) :map(f3) :map(f4) :reduce(acc_fn, 0) print('sum:', sum)
See functions task1 and task2 in the sample code for functions that do these two tasks.
Lazy evaluation -- After running this script, if you inspect that messages that are printed out, you will notice that functions f1, f2, f3, and f4 all complete for the first item in the collection, before processing is begun for the next item. That will enable us to process large collections and to incrementally obtain results for some items in the collection without waiting for all the items in the collection.