Repository: rtsisyk/luafun
Branch: master
Commit: 12837884993a
Files: 52
Total size: 132.2 KB
Directory structure:
gitextract_diaikeab/
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── COPYING.md
├── HACKING.md
├── README.md
├── debian/
│ ├── .gitignore
│ ├── changelog
│ ├── compat
│ ├── control
│ ├── copyright
│ ├── lua-fun.docs
│ ├── lua5.1.dh-lua.conf
│ ├── patches/
│ │ └── series
│ ├── rules
│ ├── source/
│ │ └── format
│ └── watch
├── doc/
│ ├── .gitignore
│ ├── Makefile
│ ├── _static/
│ │ └── .keep
│ ├── _templates/
│ │ └── layout.html
│ ├── about.rst
│ ├── basic.rst
│ ├── compositions.rst
│ ├── conf.py
│ ├── filtering.rst
│ ├── generators.rst
│ ├── getting_started.rst
│ ├── index.rst
│ ├── indexing.rst
│ ├── intro.rst
│ ├── operators.rst
│ ├── reducing.rst
│ ├── reference.rst
│ ├── slicing.rst
│ ├── transformations.rst
│ └── under_the_hood.rst
├── fun-scm-1.rockspec
├── fun.lua
├── rpm/
│ └── lua-fun.spec
└── tests/
├── .gitignore
├── basic.lua
├── compositions.lua
├── filtering.lua
├── generators.lua
├── indexing.lua
├── operators.lua
├── reducing.lua
├── runtest
├── slicing.lua
├── stateful.lua
└── transformations.lua
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*~
temp/
fun.lua.c
5.?-fun/
================================================
FILE: .travis.yml
================================================
sudo: false
language: C
services:
- docker
env:
global:
- PRODUCT=lua-fun
matrix:
- OS=el DIST=7
- OS=fedora DIST=24
- OS=fedora DIST=25
- OS=ubuntu DIST=xenial
- OS=ubuntu DIST=yakkety
- OS=debian DIST=stretch
before_deploy:
- git clone https://github.com/packpack/packpack.git packpack
- ./packpack/packpack
deploy:
provider: packagecloud
username: ${PACKAGECLOUD_USER}
repository: ${PACKAGECLOUD_REPO}
token: ${PACKAGECLOUD_TOKEN}
dist: ${OS}/${DIST}
package_glob: build/*.{deb,rpm}
skip_cleanup: true
on:
branch: master
condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}"
after_deploy:
# Prune old packages from PackageCloud, keep only the last two
- pip install -r ./packpack/tools/requirements.txt
- python ./packpack/tools/packagecloud prune ${PACKAGECLOUD_USER}/${PACKAGECLOUD_REPO} deb ${OS} ${DIST} --keep 2
- python ./packpack/tools/packagecloud prune ${PACKAGECLOUD_USER}/${PACKAGECLOUD_REPO} rpm ${OS} ${DIST} --keep 2
cache:
directories:
- $HOME/lua-5.3.2
addons:
apt:
packages:
- lua5.1
- lua5.2
- luajit
# Ubuntu Precise on Travis doesn't have lua5.3 package
install:
- |
[ -e ${HOME}/lua-5.3.2/src/lua ] || (\
wget http://www.lua.org/ftp/lua-5.3.2.tar.gz -c && \
tar xzf lua-5.3.2.tar.gz -C ${HOME} && \
make -j -C ${HOME}/lua-5.3.2 linux \
)
script:
- cd tests
- LUAJIT=`echo /usr/bin/luajit* | cut -f 1 -d ' '`
- ${LUAJIT} -v
- ${LUAJIT} runtest *.lua
- lua5.1 -v
- lua5.1 runtest *.lua
- lua5.2 -v
- lua5.2 runtest *.lua
- LUA53=${HOME}/lua-5.3.2/src/lua
- ${LUA53} -v
- ${LUA53} runtest *.lua
- cd ..
notifications:
email: true
================================================
FILE: CONTRIBUTING.md
================================================
Contributing
============
We'd love for you to contribute to the project and make **Lua Fun** even better
than it is today!
Filling Issues
---------------
Please file bugs reports and feature requests using [GitHub Issues].
[GitHub Issues]: https://github.com/luafun/luafun/issues
Making Changes
--------------
If you want to contribute code, please fork the project on [GitHub], make
changes in branch and send a pull request.
[GitHub]: https://github.com/luafun/luafun
================================================
FILE: COPYING.md
================================================
Copying
=======
**Lua Fun** source codes, logo and documentation are distributed under the
**[MIT/X11 License]** - same as Lua and LuaJIT.
Copyright (c) 2013-2017 Roman Tsisyk <roman@tsisyk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
[MIT/X11 License]: http://www.opensource.org/licenses/mit-license.php
================================================
FILE: HACKING.md
================================================
# Hacking
## Build documentation
Build and open in a Web browser:
```sh
$ cd doc
$ make html
$ xdg-open _build/html/index.html
```
It works with Sphinx 4.3.0 and likely will work with newer versions.
================================================
FILE: README.md
================================================
Lua Functional
==============
<img src="/doc/logo.png" align="right" width="174px" height="144px" />
**Lua Fun** is a high-performance functional programming library for [Lua]
designed with [LuaJIT's trace compiler][LuaJIT] in mind.
Lua Fun provides a set of more than 50 programming primitives typically
found in languages like Standard ML, Haskell, Erlang, JavaScript, Python and
even Lisp. High-order functions such as ``map``, ``filter``, ``reduce``,
``zip``, etc., make it easy to **write simple and efficient functional code**.
Let's see an example:
```lua
> -- Functional style
> require "fun" ()
> -- calculate sum(x for x^2 in 1..n)
> n = 100
> print(reduce(operator.add, 0, map(function(x) return x^2 end, range(n))))
328350
> -- Object-oriented style
> local fun = require "fun"
> -- calculate sum(x for x^2 in 1..n)
> print(fun.range(n):map(function(x) return x^2 end):reduce(operator.add, 0))
328350
```
**Lua Fun** takes full advantage of the innovative **tracing JIT compiler**
to achieve transcendental performance on nested functional expressions.
Functional compositions and high-order functions can be translated into
**efficient machine code**. Can you believe it? Just try to run the example
above with ``luajit -jdump`` and see what happens:
```asm
-- skip some initialization code --
->LOOP:
0bcaffd0 movaps xmm5, xmm7
0bcaffd3 movaps xmm7, xmm1
0bcaffd6 addsd xmm7, xmm5
0bcaffda ucomisd xmm7, xmm0
0bcaffde jnb 0x0bca0024 ->5
0bcaffe4 movaps xmm5, xmm7
0bcaffe7 mulsd xmm5, xmm5
0bcaffeb addsd xmm6, xmm5
0bcaffef jmp 0x0bcaffd0 ->LOOP
---- TRACE 1 stop -> loop
```
The functional chain above was translated by LuaJIT to (!) **one machine loop**
containing just 10 CPU assembly instructions without CALL. Unbelievable!
Readable? Efficient? Can your Python/Ruby/V8 do better?
Status
------
**Lua Fun** is in an early alpha stage. The library fully
[documented][Documentation] and covered with unit tests.
[][Travis]
LuaJIT 2.1 alpha is recommended. The library designed in mind of fact that
[LuaJIT traces tail-, up- and down-recursion][LuaJIT-Recursion] and has a lot of
[byte code optimizations][LuaJIT-Optimizations]. Lua 5.1-5.3 are also
supported.
This is **master** (development) branch. API may be changed without any special
notice. Please use **stable** branch for your production deployments.
If you still want to use **master**, please don't forget to grep `git log`
for *Incompatible API changes* message. Thanks!
Please check out [documentation][Documentation] for more information.
Misc
----
**Lua Fun** is distributed under the [MIT/X11 License] -
(same as Lua and LuaJIT).
The library was written to use with [Tarantool] - an efficient in-memory
store and an asynchronous Lua application server.
See Also
--------
* [Documentation]
* [RockSpec]
* [RPM/DEB packages](https://packagecloud.io/rtsisyk/master)
* [Hacking]
* lua-l@lists.lua.org
* luajit@freelists.org
* roman@tsisyk.com
[Lua]: https://www.lua.org/
[LuaJIT]: https://luajit.org/luajit.html
[LuaJIT-Recursion]: http://lambda-the-ultimate.org/node/3851#comment-57679
[LuaJIT-Optimizations]: http://wiki.luajit.org/Optimizations
[MIT/X11 License]: https://opensource.org/licenses/MIT
[Tarantool]: https://github.com/tarantool/tarantool
[Getting Started]: https://luafun.github.io/getting_started.html
[Documentation]: https://luafun.github.io/
[Travis]: https://travis-ci.org/luafun/luafun
[RockSpec]: https://raw.github.com/luafun/luafun/master/fun-scm-1.rockspec
[Hacking]: HACKING.md
Please **"Star"** the project on GitHub to help it to survive! Thanks!
*****
**Lua Fun**. Simple, Efficient and Functional. In Lua. With JIT.
================================================
FILE: debian/.gitignore
================================================
lua-fun/
tmp/
trash
files
lua_versions
*.install
*.substvars
*.log
================================================
FILE: debian/changelog
================================================
lua-fun (0.1.3-1) unstable; urgency=medium
* Initial release. (Closes: #811482)
-- Roman Tsisyk <roman@tarantool.org> Mon, 18 Jan 2016 10:00:00 +0300
================================================
FILE: debian/compat
================================================
9
================================================
FILE: debian/control
================================================
Source: lua-fun
Section: interpreters
Priority: optional
Maintainer: Roman Tsisyk <roman@tarantool.org>
Build-Depends: debhelper (>= 9), dh-lua (>= 19)
Standards-Version: 3.9.6
Homepage: https://github.com/luafun/luafun
Vcs-Git: git://github.com/luafun/luafun.git
Vcs-Browser: https://github.com/luafun/luafun
Package: lua-fun
Architecture: all
Depends: ${misc:Depends}
Provides: ${lua:Provides}
XB-Lua-Versions: ${lua:Versions}
Description: High-performance functional programming library for Lua
Lua Fun provides a set of more than 50 programming primitives typically
found in languages like Standard ML, Haskell, Erlang, JavaScript, Python and
even Lisp. High-order functions such as map, filter, reduce, zip, etc.,
make it easy to write simple and efficient functional code.
================================================
FILE: debian/copyright
================================================
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: luafun
Upstream-Contact: Roman Tsisyk <roman@tsisyk.com>
Source: https://github.com/luafun/luafun
Files: *
Copyright: 2013-2017 Roman Tsisyk <roman@tsisyk.com>
Comment: In the Lua community this license is better known as "MIT".
Unfortunately other variants of this license are also known as "MIT".
To obtain a machine interpretable copyright file Debian prefers to name this
version of the MIT license using the non ambiguous term "Expat".
License: Expat
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
“Software”), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: debian/lua-fun.docs
================================================
README.md
================================================
FILE: debian/lua5.1.dh-lua.conf
================================================
PKG_NAME=fun
LUA_MODNAME=fun
LUA_SOURCES=fun.lua
LUA_TEST=tests/runtest tests/*.lua
================================================
FILE: debian/patches/series
================================================
================================================
FILE: debian/rules
================================================
#!/usr/bin/make -f
VERSION := $(shell dpkg-parsechangelog|grep ^Version|awk '{print $$2}')
UVERSION := $(shell echo $(VERSION)|sed 's/-[[:digit:]]\+$$//')
%:
dh $@ --buildsystem=lua --with lua
tarball: clean
tar --exclude=.git --exclude=debian --exclude rpm \
--transform='s,^\.,luafun-$(UVERSION),S' \
-czf ../luafun-$(UVERSION).orig.tar.gz .
================================================
FILE: debian/source/format
================================================
3.0 (quilt)
================================================
FILE: debian/watch
================================================
# test this watch file using:
# uscan --watchfile debian/watch --upstream-version 0.0.1 --package lua-fun
# https://wiki.debian.org/debian/watch#GitHub
version=3
opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/luafun-$1\.tar\.gz/ \
https://github.com/luafun/luafun/tags .*/v?(\d\S*)\.tar\.gz
================================================
FILE: doc/.gitignore
================================================
_build
================================================
FILE: doc/Makefile
================================================
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
================================================
FILE: doc/_static/.keep
================================================
================================================
FILE: doc/_templates/layout.html
================================================
{% extends "!layout.html" %}
{% block footer %}
{{ super() }}
<script type="text/javascript">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-45899190-2', 'auto');
ga('send', 'pageview');
</script>
{% endblock %}
================================================
FILE: doc/about.rst
================================================
About
=====
Credits
-------
An initial prototype was designed and enginered in one evening by Roman Tsisyk.
After that the library was completely rewritten, tested and documented
(which took a while).
The project exists only thanks to the excellent tracing just-in-time compiler
in `LuaJIT <http://luajit.org>`_.
The library works best with `Tarantool <http://tarantool.org>`_ --
an efficient in-memory database and Lua application server.
Copying
-------
Lua Fun source codes, logo and documentation are distributed under the
`MIT License (MIT) <http://www.opensource.org/licenses/mit-license.php>`_ --
same as LuaJIT.
Copyright (c) 2013-2017 Roman Tsisyk <roman@tsisyk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: doc/basic.rst
================================================
Basic Functions
===============
.. currentmodule:: fun
The section contains functions to create iterators from Lua objects.
.. function:: iter(array)
iter(map)
iter(string)
iter(gen, param, state)
:returns: ``gen, param, state`` -- :ref:`iterator triplet <iterator_triplet>`
Make ``gen, param, state`` iterator from the iterable object.
The function is a generalized version of :func:`pairs` and :func:`ipairs`.
The function distinguish between arrays and maps using ``#arg == 0``
check to detect maps. For arrays ``ipairs`` is used. For maps a modified
version of ``pairs`` is used that also returns keys. Userdata objects
are handled in the same way as tables.
If ``LUAJIT_ENABLE_LUA52COMPAT`` [#luajit_lua52compat]_ mode is enabled and
argument has metamethods ``__pairs`` (for maps) or ``__ipairs`` for (arrays),
call it with the table or userdata as argument and return the first three
results from the call [#lua52_ipairs]_.
All library iterator are suitable to use with Lua's ``for .. in`` loop.
.. code-block:: lua
> for _it, a in iter({1, 2, 3}) do print(a) end
1
2
3
> for _it, k, v in iter({ a = 1, b = 2, c = 3}) do print(k, v) end
b 2
a 1
c 3
> for _it, a in iter("abcde") do print(a) end
a
b
c
d
e
The first cycle variable *_it* is needed to store an internal state of
the iterator. The value must be always ignored in loops:
.. code-block:: lua
for _it, a, b in iter({ a = 1, b = 2, c = 3}) do print(a, b) end
-- _it is some internal iterator state - always ignore it
-- a, b are values return from the iterator
Simple iterators like ``iter({1, 2, 3})`` have simple states, whereas
other iterators like :func:`zip` or :func:`chain` have complicated
internal states which values senseless for the end user.
Check out :doc:`under_the_hood` section for more details.
There is also the possibility to supply custom iterators to the
function:
.. code-block:: lua
> local function mypairs_gen(max, state)
if (state >= max) then
return nil
end
return state + 1, state + 1
end
> local function mypairs(max)
return mypairs_gen, max, 0
end
> for _it, a in iter(mypairs(10)) do print(a) end
1
2
3
4
5
6
7
8
9
10
Iterators can return multiple values.
Check out :doc:`under_the_hood` section for more details.
.. [#luajit_lua52compat] http://luajit.org/extensions.html
.. [#lua52_ipairs] http://www.lua.org/manual/5.2/manual.html#pdf-ipairs
.. function:: each(fun, gen, param, state)
iterator:each(fun)
:returns: none
Execute the *fun* for each iteration value. The function is equivalent to
the code below:
.. code-block:: lua
for _it, ... in iter(gen, param, state) do
fun(...)
end
Examples:
.. code-block:: lua
> each(print, { a = 1, b = 2, c = 3})
b 2
a 1
c 3
> each(print, {1, 2, 3})
1
2
3
The function is used for its side effects. Implementation directly applies
*fun* to all iteration values without returning a new iterator, in contrast
to functions like :func:`map`.
.. seealso:: :func:`map`, :func:`reduce`
.. function:: for_each(fun, gen, param, state)
iterator:for_each(fun)
An alias for :func:`each`.
.. function:: foreach(fun, gen, param, state)
iterator:foreach(fun)
An alias for :func:`each`.
================================================
FILE: doc/compositions.rst
================================================
Compositions
============
.. currentmodule:: fun
.. function:: zip(...)
iterator1:zip(iterator2, iterator3, ...)
:param ...: iterators to "zip"
:type ...: iterator
:returns: an iterator
Return a new iterator where i-th return value contains the i-th element
from each of the iterators. The returned iterator is truncated in length
to the length of the shortest iterator. For multi-return iterators only the
first variable is used.
Examples:
.. code-block:: lua
> dump(zip({"a", "b", "c", "d"}, {"one", "two", "three"}))
a one
b two
c three
> each(print, zip())
> each(print, zip(range(5), {'a', 'b', 'c'}, rands()))
1 a 0.57514179487402
2 b 0.79693061238668
3 c 0.45174307459403
> each(print, zip(partition(function(x) return x > 7 end, range(1, 15, 1))))
8 1
9 2
10 3
11 4
12 5
13 6
14 7
.. function:: cycle(gen, param, state)
iterator:cycle()
:returns: a cycled version of ``{gen, param, state}`` iterator
Make a new iterator that returns elements from ``{gen, param, state}``
iterator until the end and then "restart" iteration using a saved clone of
``{gen, param, state}``. The returned iterator is constant space and no
return values are buffered. Instead of that the function make a clone of the
source ``{gen, param, state}`` iterator. Therefore, the source iterator
must be pure functional to make an identical clone. Infinity iterators
are supported, but are not recommended.
.. note:: ``{gen, param, state}`` must be pure functional to work properly
with the function.
Examples:
.. code-block:: lua
> each(print, take(15, cycle(range(5))))
1
2
3
4
5
1
2
3
4
5
1
2
3
4
5
> each(print, take(15, cycle(zip(range(5), {"a", "b", "c", "d", "e"}))))
1 a
2 b
3 c
4 d
5 e
1 a
2 b
3 c
4 d
5 e
1 a
2 b
3 c
4 d
5 e
.. function:: chain(...)
iterator1:chain(iterator2, iterator3, ...)
:param ...: iterators to chain
:type ...: iterator
:returns: a consecutive iterator from sources (left from right)
Make an iterator that returns elements from the first iterator until it is
exhausted, then proceeds to the next iterator, until all of the iterators
are exhausted. Used for treating consecutive iterators as a single iterator.
Infinity iterators are supported, but are not recommended.
Examples:
.. code-block:: lua
> each(print, chain(range(2), {"a", "b", "c"}, {"one", "two", "three"}))
1
2
a
b
c
one
two
three
> each(print, take(15, cycle(chain(enumerate({"a", "b", "c"}),
{"one", "two", "three"}))))
1 a
2 b
3 c
one
two
three
1 a
2 b
3 c
one
two
three
1 a
2 b
3 c
================================================
FILE: doc/conf.py
================================================
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'Lua Functional'
copyright = '2013-2021, Roman Tsisyk'
author = 'Roman Tsisyk'
# The short X.Y version
version = '0.1'
# The full version, including alpha/beta/rc tags
release = '0.1.3'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'haiku'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
html_logo = 'logo.png'
# If true, the reST sources are included in the HTML build as _sources/name.
# The default is True.
html_copy_source = False
================================================
FILE: doc/filtering.rst
================================================
Filtering
=========
.. currentmodule:: fun
This section contains functions to filter values during iteration.
.. function:: filter(predicate, gen, param, state)
iterator:filter(predicate)
:param param: an predicate to filter the iterator
:type param: (function(...) -> bool)
Return a new iterator of those elements that satisfy the **predicate**.
Examples:
.. code-block:: lua
> each(print, filter(function(x) return x % 3 == 0 end, range(10)))
3
6
9
> each(print, take(5, filter(function(i, x) return i % 3 == 0 end,
enumerate(duplicate('x')))))
3 x
6 x
9 x
12 x
15 x
.. note:: Multireturn iterators are supported but can cause performance
regressions.
.. seealso:: :func:`take_while` and :func:`drop_while`.
.. function:: remove_if(predicate, gen, param, state)
iterator:remove_if(predicate)
An alias for :func:`filter`.
.. function:: grep(regexp_or_predicate, gen, param, state)
iterator:grep(regexp_or_predicate)
If **regexp_or_predicate** is string then the parameter is used as a regular
expression to build filtering predicate. Otherwise the function is just an
alias for :func:`filter`.
Equivalent to:
.. code-block:: lua
local fun = regexp_or_predicate
if type(regexp_or_predicate) == "string" then
fun = function(x) return string.find(x, regexp_or_predicate) ~= nil end
end
return filter(fun, gen, param, state)
Examples:
.. code-block:: lua
lines_to_grep = {
[[Emily]],
[[Chloe]],
[[Megan]],
[[Jessica]],
[[Emma]],
[[Sarah]],
[[Elizabeth]],
[[Sophie]],
[[Olivia]],
[[Lauren]]
}
each(print, grep("^Em", lines_to_grep))
--[[test
Emily
Emma
--test]]
each(print, grep("^P", lines_to_grep))
--[[test
--test]]
> each(print, grep(function(x) return x % 3 == 0 end, range(10)))
3
6
9
.. function:: partition(predicate, gen, param, state)
iterator:partition(predicate)
:param x: a value to find
:returns: {gen1, param1, state1}, {gen2, param2, state2}
The function returns two iterators where elements do and do not satisfy the
prediucate. Equivalent to:
.. code-block:: lua
return filter(predicate, gen', param', state'),
filter(function(...) return not predicate(...) end, gen, param, state);
The function make a clone of the source iterator. Iterators especially
returned in tables to work with :func:`zip` and other functions.
Examples:
.. code-block:: lua
> each(print, zip(partition(function(i, x) return i % 3 == 0 end, range(10))))
3 1
6 2
9 4
.. note:: ``gen, param, state`` must be pure functional to work properly
with the function.
.. seealso:: :func:`span`
================================================
FILE: doc/generators.rst
================================================
Generators
==========
.. currentmodule:: fun
This section contains a number of useful generators modeled after Standard ML,
Haskell, Python, Ruby, JavaScript and other languages.
Finite Generators
-----------------
.. function:: range([start,] stop[, step])
:param start: an endpoint of the interval (see below)
:type start: number
:param stop: an endpoint of the interval (see below)
:type stop: number
:param step: a step
:type step: number
:returns: an iterator
The iterator to create arithmetic progressions. Iteration values are generated
within closed interval ``[start, stop]`` (i.e. *stop* is included).
If the *start* argument is omitted, it defaults to ``1`` (*stop* > 0) or
to ``-1`` (*stop* < 0). If the *step* argument is omitted, it defaults to
``1`` (*start* <= *stop*) or to ``-1`` (*start* > *stop*). If *step* is
positive, the last element is the largest ``start + i * step`` less than or
equal to *stop*; if *step* is negative, the last element is the smallest
``start + i * step`` greater than or equal to *stop*.
*step* must not be zero (or else an error is raised).
``range(0)`` returns empty iterator.
Examples:
.. code-block:: lua
> for _it, v in range(5) do print(v) end
1
2
3
4
5
> for _it, v in range(-5) do print(v) end
-1
-2
-3
-4
-5
> for _it, v in range(1, 6) do print(v) end
1
2
3
4
5
6
> for _it, v in range(0, 20, 5) do print(v) end
0
5
10
15
20
> for _it, v in range(0, 10, 3) do print(v) end
0
3
6
9
> for _it, v in range(0, 1.5, 0.2) do print(v) end
0
0.2
0.4
0.6
0.8
1
1.2
1.4
> for _it, v in range(0) do print(v) end
> for _it, v in range(1) do print(v) end
1
> for _it, v in range(1, 0) do print(v) end
1
0
> for _it, v in range(0, 10, 0) do print(v) end
error: step must not be zero
Infinity Generators
-------------------
.. function:: duplicate(...)
:param ...: objects to duplicate
:type ...: non nil
:returns: an iterator
The iterator returns values over and over again indefinitely. All values
that passed to the iterator are returned as-is during the iteration.
Examples:
.. code-block:: lua
> each(print, take(3, duplicate('a', 'b', 'c')))
a b c
a b c
> each(print, take(3, duplicate('x')))
x
x
x
> for _it, a, b, c, d, e in take(3, duplicate(1, 2, 'a', 3, 'b')) do
print(a, b, c, d, e)
>> end
1 2 a 3 b
1 2 a 3 b
1 2 a 3 b
.. function:: xrepeat(...)
An alias for :func:`duplicate`.
.. function:: replicate(...)
An alias for :func:`duplicate`.
.. function:: tabulate(fun)
:param fun: an unary generating function
:type fun: function(n: uint) -> ...
:returns: an iterator
The iterator that returns ``fun(0)``, ``fun(1)``, ``fun(2)``, ``...`` values
indefinitely.
Examples:
.. code-block:: lua
> each(print, take(5, tabulate(function(x) return 'a', 'b', 2*x end)))
a b 0
a b 2
a b 4
a b 6
a b 8
> each(print, take(5, tabulate(function(x) return x^2 end)))
0
1
4
9
16
.. function:: zeros()
:returns: an iterator
The iterator returns ``0`` indefinitely.
Examples:
.. code-block:: lua
> each(print, take(5, zeros()))
0
0
0
0
0
.. function:: ones()
:returns: an iterator
The iterator that returns ``1`` indefinitely.
Example::
> each(print, take(5, ones()))
1
1
1
1
1
Random sampling
---------------
.. function:: rands([n[, m]])
:param n: an endpoint of the interval (see below)
:type n: uint
:param m: an endpoint of the interval (see below)
:type m: uint
:returns: an iterator
The iterator returns random values using :func:`math.random`.
If the **n** and **m** are set then the iterator returns pseudo-random
integers in the ``[n, m)`` interval (i.e. **m** is not included).
If the **m** is not set then the iterator generates pseudo-random integers
in the ``[0, n)`` interval. When called without arguments returns
pseudo-random real numbers with uniform distribution in the
interval ``[0, 1)``.
.. warning:: This iterator is not pure-functional and may not work as
expected with some library functions.
Examples:
.. code-block:: lua
> each(print, take(10, rands(10, 20)))
19
17
11
19
12
13
14
16
10
11
> each(print, take(5, rands(10)))
7
6
5
9
0
> each(print, take(5, rands()))
0.79420629243124
0.69885246563716
0.5901037417281
0.7532286166836
0.080971251199854
================================================
FILE: doc/getting_started.rst
================================================
Getting Started
===============
Please jump to `Using the Library`_ section if you are familiar with Lua and
LuaJIT.
.. contents::
Prerequisites
-------------
The library is designed for LuaJIT_. **LuaJIT 2.1 alpha** is high^W **Highly**
recommended for performance reasons. Lua 5.1--5.3 are also supported.
The library is platform-independent and expected to work on all platforms that
supported by Lua(JIT). It can be also used in any Lua(JIT) based applications,
e.g. Tarantool_ or OpenResty_.
You might need diff_ tool to run test system and sphinx_ to regenerate the
documentation from source files.
.. _LuaJIT: http://luajit.org/
.. _Tarantool: http://tarantool.org/
.. _OpenResty: http://openresty.org/
.. _diff: http://en.wikipedia.org/wiki/Diff
.. _sphinx: http://sphinx-doc.org/
Installing LuaJIT
-----------------
You can build LuaJIT from sources or install it from a binary archive.
From Sources
````````````
1. Clone LuaJIT git repository. Please note that **v2.1** branch is needed.
You can always select this branch using ``git checkout v2.1``.
.. code-block:: bash
$ git clone http://luajit.org/git/luajit-2.0.git -b v2.1 luajit-2.1
Cloning into 'luajit-2.1'...
2. Compile LuaJIT
.. code-block:: bash
$ cd luajit-2.1/
luajit-2.1 $ make -j8
3. Install LuaJIT
.. code-block:: bash
luajit-2.1 $ make install
luajit-2.1 $ ln -s /usr/local/bin/luajit-2.1.0-alpha /usr/local/bin/luajit
Install operation might require root permissions. However, you can install
LuaJIT into your home directory.
From a Binary Archive
`````````````````````
If operations above look too complicated for you, you always can download a
binary archive from http://luajit.org/download.html page.
Your favorite package manager may also have LuaJIT packages.
Running LuaJIT
``````````````
Ensure that freshly installed LuaJIT works:
.. code-block:: bash
$ luajit
LuaJIT 2.1.0-alpha -- Copyright (C) 2005-2013 Mike Pall. http://luajit.org/
JIT: ON SSE2 SSE3 SSE4.1 fold cse dce fwd dse narrow loop abc sink fuse
> = 2 + 2
4
It is good idea to use LuaJIT CLI under ``rlwrap`` (on nix platforms):
.. code-block:: bash
alias luajit="rlwrap luajit"
$ luajit
LuaJIT 2.1.0-alpha -- Copyright (C) 2005-2013 Mike Pall. http://luajit.org/
JIT: ON SSE2 SSE3 SSE4.1 fold cse dce fwd dse narrow loop abc sink fuse
> = 2 + 2
4
> = 2 + 2 <!-- You can use arrows, completion and so on, like in Bash
Installing the Library
----------------------
Using LuaRocks
``````````````
Use the rockspec_ file.
.. _rockspec: https://raw.github.com/luafun/luafun/master/fun-scm-1.rockspec
Using git
`````````
1. Clone Lua Fun repository:
.. code-block:: bash
git clone git://github.com/luafun/luafun.git
$ cd luafun
2. Run tests (optional):
.. code-block:: bash
luafun $ cd tests
luafun/tests $ ./runtest *.lua
Testing basic.lua
Testing compositions.lua
Testing filters.lua
Testing folds.lua
Testing generators.lua
Testing slices.lua
Testing transformations.lua
All tests have passed!
Using wget
``````````
Just download https://raw.github.com/luafun/luafun/master/fun.lua file:
.. code-block:: bash
$ wget https://raw.github.com/luafun/luafun/master/fun.lua
Using the Library
-----------------
Try to run LuaJIT in the same directory where ``fun.lua`` file is located:
.. code-block:: bash
:emphasize-lines: 4
luafun $ luajit
LuaJIT 2.1.0-alpha -- Copyright (C) 2005-2013 Mike Pall. http://luajit.org/
JIT: ON SSE2 SSE3 fold cse dce fwd dse narrow loop abc sink fuse
> fun = require 'fun'
>
> for _k, a in fun.range(3) do print(a) end
1
2
3
If you see an error message like ``stdin:1: module 'fun' not found:`` then
you need to configure you Package Path (``package.path``). Please consult
`Lua Wiki <http://lua-users.org/wiki/PackagePath>`_ for additional information.
**Lua Fun** designed to be small ubiquitous library. It is a good idea to import
all library functions to the global table:
.. code-block:: bash
:emphasize-lines: 1
> for k, v in pairs(require "fun") do _G[k] = v end -- import fun.*
> for _k, a in range(3) do print(a) end
0
1
2
**Lua Fun** also provides a special **shortcut** to autoimport all functions:
.. code-block:: bash
:emphasize-lines: 1
> require 'fun'() -- to import all lua.* functions to globals
> each(print, range(5))
1
2
3
4
5
Now you can use **Lua Fun**:
.. code-block:: bash
> print(sum(filter(function(x) return x % 16 == 0 end, range(10000))))
3130000
> each(print, take(5, tabulate(math.sin)))
0
2
4
6
8
> each(print, enumerate(zip({"one", "two", "three", "four", "five"},
{"a", "b", "c", "d", "e"})))
1 one a
2 two b
3 three c
4 four d
5 five e
> lines_to_grep = {
[[Emily]],
[[Chloe]],
[[Megan]],
[[Jessica]],
[[Emma]],
[[Sarah]],
[[Elizabeth]],
[[Sophie]],
[[Olivia]],
[[Lauren]]
}
> each(print, grep("Em", lines_to_grep))
Emily
Emma
> each(print, take(10, cycle(chain(
{enumerate({"a", "b", "c"})},
{"one", "two", "three"}))
))
0 a
1 b
2 c
one
two
three
0 a
1 b
2 c
one
Please note that functions support multireturn.
Further Actions
---------------
- Take a look on :doc:`reference`.
- Use :ref:`genindex` to find functions by its names.
- Checkout **examples** from
`tests/ <https://github.com/luafun/luafun/tree/master/tests>`_ directory
- Read :doc:`under_the_hood` section
- "Star" us the on GitHub_ to help the project to survive
- Make Great Software
- Have fun
**Lua Fun**. Simple, Efficient and Functional. In Lua. With JIT.
.. _GitHub: http://github.com/luafun/luafun
================================================
FILE: doc/index.rst
================================================
.. _library-index:
###############################
Lua Functional Library
###############################
:Release: |version|
:Date: |today|
.. highlight:: lua
Contents
========
.. toctree::
:maxdepth: 2
intro.rst
getting_started.rst
reference.rst
under_the_hood.rst
about.rst
Indices and tables
==================
* :ref:`genindex`
* :ref:`search`
================================================
FILE: doc/indexing.rst
================================================
Indexing
========
.. currentmodule:: fun
This section contains functions to find elements by its values.
.. function:: index(x, gen, param, state)
iterator:index(x)
:param x: a value to find
:returns: the position of the first element that equals to the **x**
The function returns the position of the first element in the given iterator
which is equal (using ``==``) to the query element, or ``nil`` if there is
no such element.
Examples:
.. code-block:: lua
> print(index(2, range(0)))
nil
> print(index("b", {"a", "b", "c", "d", "e"}))
2
.. function:: index_of(x, gen, param, state)
iterator:index_of(x)
An alias for :func:`index`.
.. function:: elem_index(x, gen, param, state)
iterator:elem_index(x)
An alias for :func:`index`.
.. function:: indexes(x, gen, param, state)
iterator:indexes(x)
:param x: a value to find
:returns: an iterator which positions of elements that equal to the **x**
The function returns an iterator to positions of elements which equals to
the query element.
Examples:
.. code-block:: lua
> each(print, indexes("a", {"a", "b", "c", "d", "e", "a", "b", "a", "a"}))
1
6
9
10
.. seealso:: :func:`filter`
.. function:: indices(x, gen, param, state)
iterator:indices(x)
An alias for :func:`indexes`.
.. function:: elem_indexes(x, gen, param, state)
iterator:elem_indexes(x)
An alias for :func:`indexes`.
.. function:: elem_indices(x, gen, param, state)
iterator:elem_indices(x)
An alias for :func:`indexes`.
================================================
FILE: doc/intro.rst
================================================
Introduction
============
.. currentmodule:: fun
**Lua Fun** is a high-performance functional programming library
designed for `LuaJIT tracing just-in-time compiler
<http://luajit.org/luajit.html>`_.
The library provides a set of more than 50 programming primitives typically
found in languages like Standard ML, Haskell, Erlang, JavaScript, Python and
even Lisp. High-order functions such as :func:`map`, :func:`filter`,
:func:`reduce`, :func:`zip` will help you to **write simple and efficient
functional code**.
Let's see an example:
.. code-block:: lua
:emphasize-lines: 2, 4
-- Functional style
require "fun" ()
n = 100
x = sum(map(function(x) return x^2 end, take(n, tabulate(math.sin))))
-- calculate sum(sin(x)^2 for x in 0..n-1)
print(x)
50.011981355266
.. code-block:: lua
:emphasize-lines: 2, 4
-- Object-oriented style
local fun = require "fun"
n = 100
x = fun.tabulate(math.sin):take(n):map(function(x) return x^2 end):sum()
-- calculate sum(sin(x)^2 for x in 0..n-1)
print(x)
50.011981355266
**Lua Fun** takes full advantage of the innovative **tracing JIT compiler**
to achieve transcendental performance on nested functional expressions.
Functional compositions and high-order functions can be translated into
**efficient machine code**. Can you believe it? Just try to run the example above
with ``luajit -jdump`` and see what happens:
.. code-block:: none
:emphasize-lines: 2,14
-- skip some initialization code --
->LOOP:
0bcaffd0 movsd [rsp+0x8], xmm7
0bcaffd6 addsd xmm4, xmm5
0bcaffda ucomisd xmm6, xmm1
0bcaffde jnb 0x0bca0028 ->6
0bcaffe4 addsd xmm6, xmm0
0bcaffe8 addsd xmm7, xmm0
0bcaffec fld qword [rsp+0x8]
0bcafff0 fsin
0bcafff2 fstp qword [rsp]
0bcafff5 movsd xmm5, [rsp]
0bcafffa mulsd xmm5, xmm5
0bcafffe jmp 0x0bcaffd0 ->LOOP
---- TRACE 1 stop -> loop
The functional chain above was translated by LuaJIT to (!) **one machine loop**
containing just 10 CPU assembly instructions without CALL. Unbelievable!
Readable? Efficient? Can your Python/Ruby/V8 do better?
================================================
FILE: doc/operators.rst
================================================
Operators
=========
.. module:: fun.operator
This auxiliary module exports a set of Lua operators as intrinsic functions
to use with the library high-order primitives.
.. contents::
.. note:: **op** can be used as a shortcut to **operator**.
Comparison operators
--------------------
.. seealso:: `Lua Relational Operators
<http://www.lua.org/manual/5.2/manual.html#3.4.3>`_
.. function:: le(a, b)
:returns: **a** <= **b**
.. function:: lt(a, b)
:returns: **a** < **b**
.. function:: eq(a, b)
:returns: **a** == **b**
.. function:: ne(a, b)
:returns: **a** ~= **b**
.. function:: ge(a, b)
:returns: **a** >= **b**
.. function:: gt(a, b)
:returns: **a** > **b**
Arithmetic operators
--------------------
.. seealso:: `Lua Arithmetic Operators
<http://www.lua.org/manual/5.2/manual.html#3.4.1>`_
.. function:: add(a, b)
:returns: **a** + **b**
.. function:: div(a, b)
An alias for :func:`truediv`.
.. function:: truediv(a, b)
:returns: **a** / **b**
Performs "true" float division.
Examples:
.. code-block:: lua
> print(operator.div(10, 3))
3.3333333333333
> print(operator.div(-10, 3))
-3.3333333333333
.. function:: floordiv(a, b)
:returns: math.floor(**a** / **b**)
Performs division where a result is rounded down. Examples:
.. code-block:: lua
> print(operator.floordiv(10, 3))
3
> print(operator.floordiv(12, 3))
4
> print(operator.floordiv(-10, 3))
-4
> print(operator.floordiv(-12, 3))
-4
.. function:: intdiv(a, b)
Performs C-like integer division.
Equivalent to:
.. code-block:: lua
function(a, b)
local q = a / b
if a >= 0 then return math.floor(q) else return math.ceil(q) end
end
Examples:
.. code-block:: lua
> print(operator.floordiv(10, 3))
3
> print(operator.floordiv(12, 3))
4
> print(operator.floordiv(-10, 3))
-3
> print(operator.floordiv(-12, 3))
-4
.. function:: mod(a, b)
:returns: **a** % **b**
.. note:: Result has same sign as **divisor**. Modulo in Lua is defined as
``a % b == a - math.floor(a/b)*b``.
Examples:
.. code-block:: lua
:emphasize-lines: 5-6
> print(operator.mod(10, 2))
0
> print(operator.mod(10, 3))
2
print(operator.mod(-10, 3))
2 -- == -1 in C, Java, JavaScript and but not in Lua, Python, Haskell!
.. function:: neq(a)
:returns: -**a**
.. function:: unm(a)
Unary minus. An alias for :func:`neq`.
.. function:: pow(a, b)
:returns: math.pow(**a**, **b**)
.. function:: sub(a, b)
:returns: **a** - **b**
String operators
----------------
.. seealso:: `Lua Concatenation Operator
<http://www.lua.org/manual/5.2/manual.html#3.4.5>`_
.. function:: concat(a, b)
:returns: **a** .. **b**
.. function:: len(a)
:returns: # **a**
.. function:: length(a)
An alias for :func:`len`.
Logical operators
-----------------
.. seealso:: `Lua Logical Operators
<http://www.lua.org/manual/5.2/manual.html#3.4.4>`_
.. function:: land(a, b)
:returns: **a** and **b**
.. function:: lor(a, b)
:returns: **a** or **b**
.. function:: lnot(a)
:returns: not **a**
.. function:: truth(a)
:returns: not not **a**
Return ``true`` if **a** is true, and ``false`` otherwise. Examples:
.. code-block:: lua
> print(operator.truth(1))
true
> print(operator.truth(0))
true -- It is Lua, baby!
> print(operator.truth(nil))
false
> print(operator.truth(""))
true
> print(operator.truth({}))
true
================================================
FILE: doc/reducing.rst
================================================
Reducing
========
.. currentmodule:: fun
The section contains functions to analyze iteration values and recombine
through use of a given combining operation the results of recursively processing
its constituent parts, building up a return value
.. contents::
.. note:: An attempt to use infinity iterators with the most function from
the module causes an infinite loop.
Folds
-----
.. function:: foldl(accfun, initval, gen, param, state)
iterator:reduce(accfun, initval)
:param accfun: an accumulating function
:type param: (function(prevval, ...) -> val)
:param initval: an initial value that passed to **accfun** on the first
iteration
The function reduces the iterator from left to right using the binary
operator **accfun** and the initial value **initval**.
Equivalent to::
local val = initval
for _k, ... in gen, param, state do
val = accfun(val, ...)
end
return val
Examples:
.. code-block:: lua
> print(foldl(function(acc, x) return acc + x end, 0, range(5)))
15
> print(foldl(operator.add, 0, range(5)))
15
> print(foldl(function(acc, x, y) return acc + x * y; end, 0,
zip(range(1, 5), {4, 3, 2, 1})))
20
.. function:: reduce(accfun, initval, gen, param, state)
iterator:reduce(accfun, initval)
An alias to :func:`foldl`.
.. function:: length(gen, param, state)
iterator:length()
Return a number of remaining elements in ``gen, param, state`` iterator.
Examples:
.. code-block:: lua
> print(length({"a", "b", "c", "d", "e"}))
5
> print(length(drop_n(3, {"a", "b", "c", "d", "e"})))
2
> print(length({}))
0
> print(length(range(0)))
0
.. warning:: An attempt to call this function on an infinite iterator will
result an infinite loop.
.. note:: This function has ``O(n)`` complexity for all iterators except
basic array and string iterators, where it has ``O(1)`` complexity.
.. function:: totable(gen, param, state)
:returns: a new table (array) from iterated values.
The function reduces the iterator from left to right using ``table.insert``.
Examples:
.. code-block:: lua
> local tab = totable("abcdef")
> print(type(tab), #tab)
table 6
> each(print, tab)
a
b
c
d
e
f
.. function:: tomap(gen, param, state)
:returns: a new table (map) from iterated values.
The function reduces the iterator from left to right using
``tab[val1] = val2`` expression.
Examples:
.. code-block:: lua
> local tab = tomap(zip(range(1, 7), 'abcdef'))
> print(type(tab), #tab)
table 6
> each(print, iter(tab))
a
b
c
d
e
f
Predicates
----------
.. function:: is_prefix_of(iterator1, iterator2)
iterator1:is_prefix_of(iterator2)
The function takes two iterators and returns ``true`` if the first iterator
is a prefix of the second.
Examples:
.. code-block:: lua
> print(is_prefix_of({"a"}, {"a", "b", "c"}))
true
> print(is_prefix_of(range(6), range(5)))
false
.. function:: is_null(gen, param, state)
iterator:is_null()
:returns: true when `gen, param, state`` iterator is empty or finished.
:returns: false otherwise.
Example::
> print(is_null({"a", "b", "c", "d", "e"}))
false
> print(is_null({}))
true
> print(is_null(range(0)))
true
.. function:: all(predicate, gen, param, state)
iterator:all(predicate)
:param predicate: a predicate
Returns true if all return values of iterator satisfy the **predicate**.
Examples:
.. code-block:: lua
> print(all(function(x) return x end, {true, true, true, true}))
true
> print(all(function(x) return x end, {true, true, true, false}))
false
.. function:: every(predicate, gen, param, state)
An alias for :func:`all`.
.. function:: any(predicate, gen, param, state)
iterator:any(predicate)
:param predicate: a predicate
Returns ``true`` if at least one return values of iterator satisfy the
**predicate**. The iteration stops on the first such value. Therefore,
infinity iterators that have at least one satisfying value might work.
Examples:
.. code-block:: lua
> print(any(function(x) return x end, {false, false, false, false}))
false
> print(any(function(x) return x end, {false, false, false, true}))
true
.. function:: some(predicate, gen, param, state)
An alias for :func:`any`.
Special folds
-------------
.. function:: sum(gen, param, state)
iterator:sum()
Sum up all iteration values. An optimized alias for::
foldl(operator.add, 0, gen, param, state)
For an empty iterator ``0`` is returned.
Examples:
.. code-block:: lua
> print(sum(range(5)))
15
.. function:: product(gen, param, state)
iterator:product()
Multiply all iteration values. An optimized alias for::
foldl(operator.mul, 1, gen, param, state)
For an empty iterator ``1`` is returned.
Examples:
.. code-block:: lua
> print(product(range(1, 5)))
120
.. function:: min(gen, param, state)
iterator:min()
Return a minimum value from the iterator using :func:`operator.min` or ``<``
for numbers and other types respectively. The iterator must be
non-null, otherwise an error is raised.
Examples:
.. code-block:: lua
> print(min(range(1, 10, 1)))
1
> print(min({"f", "d", "c", "d", "e"}))
c
> print(min({}))
error: min: iterator is empty
.. function:: minimum(gen, param, state)
An alias for :func:`min`.
.. function:: min_by(cmp, gen, param, state)
iterator:min_by(cmp)
Return a minimum value from the iterator using the **cmp** as a ``<``
operator. The iterator must be non-null, otherwise an error is raised.
Examples:
.. code-block:: lua
> function min_cmp(a, b) if -a < -b then return a else return b end end
> print(min_by(min_cmp, range(1, 10, 1)))
9
.. function:: minimum_by(cmp, gen, param, state)
An alias for :func:`min_by`.
.. function:: max(gen, param, state)
iterator:max()
Return a maximum value from the iterator using :func:`operator.max` or ``>``
for numbers and other types respectively.
The iterator must be non-null, otherwise an error is raised.
Examples:
.. code-block:: lua
> print(max(range(1, 10, 1)))
9
> print(max({"f", "d", "c", "d", "e"}))
f
> print(max({}))
error: max: iterator is empty
.. function:: maximum(gen, param, state)
An alias for :func:`max`.
.. function:: max_by(cmp, gen, param, state)
iterator:max_by(cmp)
Return a maximum value from the iterator using the **cmp** as a `>`
operator. The iterator must be non-null, otherwise an error is raised.
Examples:
.. code-block:: lua
> function max_cmp(a, b) if -a > -b then return a else return b end end
> print(max_by(max_cmp, range(1, 10, 1)))
1
.. function:: maximum_by(cmp, gen, param, state)
An alias for :func:`max_by`.
================================================
FILE: doc/reference.rst
================================================
API Reference
=============
.. module:: fun
.. toctree::
basic.rst
generators.rst
slicing.rst
indexing.rst
filtering.rst
reducing.rst
transformations.rst
compositions.rst
operators.rst
================================================
FILE: doc/slicing.rst
================================================
Slicing
=======
.. currentmodule:: fun
This section contains functions to make subsequences from iterators.
Basic
-----
.. function:: nth(n, gen, param, state)
iterator:nth(n)
:param uint n: a sequential number (indexed starting from ``1``,
like Lua tables)
:returns: **n**-th element of ``gen, param, state`` iterator
This function returns the **n**-th element of ``gen, param, state``
iterator. If the iterator does not have **n** items then ``nil`` is returned.
Examples:
.. code-block:: lua
> print(nth(2, range(5)))
2
> print(nth(10, range(5)))
nil
> print(nth(2, {"a", "b", "c", "d", "e"}))
b
> print(nth(2, drop_n(3, {"a", "b", "c", "d", "e"})))
e
> print(nth(2, enumerate({"a", "b", "c", "d", "e"})))
2 b
This function is optimized for basic array and string iterators and has
``O(1)`` complexity for these cases.
.. function:: head(gen, param, state)
iterator:head()
:returns: a first element of ``gen, param, state`` iterator
Extract the first element of ``gen, param, state`` iterator.
If the iterator is empty then an error is raised.
Examples:
.. code-block:: lua
> print(head({"a", "b", "c", "d", "e"}))
a
> print(head({}))
error: head: iterator is empty
> print(head(range(0)))
error: head: iterator is empty
> print(head(enumerate({"a", "b"})))
1 a
.. function:: car(gen, param, state)
An alias for :func:`head`.
.. function:: tail(gen, param, state)
iterator:tail()
:returns: ``gen, param, state`` iterator without a first element
Return a copy of ``gen, param, state`` iterator without its first element.
If the iterator is empty then an empty iterator is returned.
Examples:
.. code-block:: lua
> each(print, tail({"a", "b", "c", "d", "e"}))
b
c
d
e
> each(print, tail({}))
> each(print, tail(range(0)))
> each(print, tail(enumerate({"a", "b", "c"})))
2 b
3 c
.. function:: cdr(gen, param, state)
An alias for :func:`tail`.
Subsequences
------------
.. function:: take_n(n, gen, param, state)
iterator:take_n(n)
:param n: a number of elements to take
:type n: uint
:returns: an iterator on the subsequence of first **n** elements
Examples:
.. code-block:: lua
> each(print, take_n(5, range(10)))
1
2
3
4
5
> each(print, take_n(5, enumerate(duplicate('x'))))
1 x
2 x
3 x
4 x
5 x
.. function:: take_while(predicate, gen, param, state)
iterator:take_while(predicate)
:type predicate: function(...) -> bool
:returns: an iterator on the longest prefix of ``gen, param, state``
elements that satisfy **predicate**.
Examples:
.. code-block:: lua
> each(print, take_while(function(x) return x < 5 end, range(10)))
1
2
3
4
> each(print, take_while(function(i, a) return i ~=a end,
enumerate({5, 3, 4, 4, 2})))
1 5
2 3
3 4
.. seealso:: :func:`filter`
.. function:: take(n_or_predicate, gen, param, state)
iterator:take(n_or_predicate)
An alias for :func:`take_n` and :func:`take_while` that autodetects
required function based on **n_or_predicate** type.
.. function:: drop_n(n, gen, param, state)
iterator:drop_n(n)
:param n: the number of elements to drop
:type n: uint
:returns: ``gen, param, state`` iterator after skipping first **n**
elements
Examples:
.. code-block:: lua
> each(print, drop_n(2, range(5)))
3
4
5
> each(print, drop_n(2, enumerate({'a', 'b', 'c', 'd', 'e'})))
3 c
4 d
5 e
.. function:: drop_while(predicate, gen, param, state)
iterator:drop_while(predicate)
:type predicate: function(...) -> bool
:returns: ``gen, param, state`` after skipping the longest prefix
of elements that satisfy **predicate**.
Examples:
.. code-block:: lua
> each(print, drop_while(function(x) return x < 5 end, range(10)))
5
6
7
8
9
10
.. seealso:: :func:`filter`
.. function:: drop(n_or_predicate, gen, param, state)
iterator:drop(n_or_predicate)
An alias for :func:`drop_n` and :func:`drop_while` that autodetects
required function based on **n_or_predicate** type.
.. function:: span(n_or_predicate, gen, param, state)
iterator:span(n_or_predicate)
:type n_or_predicate: function(...) -> bool or uint
:returns: iterator, iterator
Return an iterator pair where the first operates on the longest prefix
(possibly empty) of ``gen, param, state`` iterator of elements that
satisfy **predicate** and second operates the remainder of
``gen, param, state`` iterator.
Equivalent to:
.. code-block:: lua
return take(n_or_predicate, gen, param, state),
drop(n_or_predicate, gen, param, state);
Examples:
.. code-block:: lua
> each(print, zip(span(function(x) return x < 5 end, range(10))))
1 5
2 6
3 7
4 8
> each(print, zip(span(5, range(10))))
1 6
2 7
3 8
4 9
5 10
.. note:: ``gen, param, state`` must be pure functional to work properly
with the function.
.. seealso:: :func:`partition`
.. function:: split(n_or_predicate, gen, param, state)
An alias for :func:`span`.
.. function:: split_at(n, gen, param, state)
An alias for :func:`span`.
================================================
FILE: doc/transformations.rst
================================================
Transformations
===============
.. currentmodule:: fun
.. function:: map(fun, gen, param, state)
iterator:map(fun)
:param fun: a function to apply
:type fun: (function(...) -> ...)
:returns: a new iterator
Return a new iterator by applying the **fun** to each element of
``gen, param, state`` iterator. The mapping is performed on the fly
and no values are buffered.
Examples:
.. code-block:: lua
> each(print, map(function(x) return 2 * x end, range(4)))
2
4
6
8
fun = function(...) return 'map', ... end
> each(print, map(fun, range(4)))
map 1
map 2
map 3
map 4
.. function:: enumerate(gen, param, state)
iterator:enumerate()
:returns: a new iterator
Return a new iterator by enumerating all elements of the
``gen, param, state`` iterator starting from ``1``. The mapping is performed
on the fly and no values are buffered.
Examples:
.. code-block:: lua
> each(print, enumerate({"a", "b", "c", "d", "e"}))
1 a
2 b
3 c
4 d
5 e
> each(print, enumerate(zip({"one", "two", "three", "four", "five"},
{"a", "b", "c", "d", "e"})))
1 one a
2 two b
3 three c
4 four d
5 five e
.. function:: intersperse(x, gen, param, state)
iterator:intersperse(x)
:type x: any
:returns: a new iterator
Return a new iterator where the **x** value is interspersed between the
elements of the source iterator. The **x** value can also be added as a
last element of returning iterator if the source iterator contains the odd
number of elements.
Examples:
.. code-block:: lua
> each(print, intersperse("x", {"a", "b", "c", "d", "e"}))
a
x
b
x
c
x
d
x
e
x
================================================
FILE: doc/under_the_hood.rst
================================================
Under the Hood
==============
.. currentmodule:: fun
The section sheds some light on the internal library structure and working
principles.
Iterators
---------
Another basic primitive of the library (after functions) is the iterator. Most functions
take an iterator and return a new iterator or several ones. Iterators all the way down!
[#iterators]_.
The simplest iterators are (surprise!) the :func:`pairs` and :func:`ipairs`
Lua functions. Have you ever tried calling, say, the :func:`ipairs` function
without using it inside a ``for`` loop? Try to do that on any Lua
implementation:
.. _iterator_triplet:
.. code-block:: bash
> =ipairs({'a', 'b', 'c'})
function: builtin#6 table: 0x40f80e38 0
The function returned three strange values which look useless without a ``for``
loop. We call these values an **iterator triplet**.
Let's see what each value is used for:
``gen`` -- first value
A generating function that can produce a next value on each iteration.
Usually returns a new ``state`` and iteration values (multireturn).
``param`` -- second value
A permanent (constant) parameter of the generating function. It is used to create
a specific instance of the generating function. For example, the table itself
in the ``ipairs`` case.
``state`` -- third value
A some transient state of an iterator that is changed after each iteration.
For example, the array index in the ``ipairs`` case.
Try calling the ``gen`` function manually:
.. code-block:: lua
> gen, param, state = ipairs({'a', 'b', 'c'})
> =gen(param, state)
1 a
The ``gen`` function returned a new state ``1`` and the next iteration
value ``a``. The second call to ``gen`` with the new state will return the next
state and the next iteration value. When the iterator gets to the end
the ``nil`` value is returned instead of the next state.
**Please do not panic!** You do not have to use these values directly.
It is just a nice trick to get ``for .. in`` loops working in Lua.
Iterations
----------
What happens when you type the following code into a Lua console::
for _it, x in ipairs({'a', 'b', 'c'}) do print(x) end
According to Lua reference manual [#lua_for]_ the code above is equivalent to::
do
-- Initialize the iterator
local gen, param, state = ipairs({'a', 'b', 'c'})
while true do
-- Next iteration
local state, var_1, ···, var_n = gen(param, state)
if state == nil then break end
-- Assign values to our variables
_it = state
x = var_1
-- Execute the code block
print(x)
end
end
What does it mean for us?
* An iterator can be used together with ``for .. in`` to generate a loop
* An iterator is fully defined by the ``gen``, ``param`` and ``state`` iterator
triplet
* The ``nil`` state marks the end of an iteration
* An iterator can return an arbitrary number of values (multireturn)
* It is possible to make some wrapping functions to take an iterator and
return a new modified iterator
**The library provides a set of iterators** that can be used like ``pairs``
and ``ipairs``.
Iterator Types
--------------
Pure functional iterators
`````````````````````````
Iterators can be either purely functional or have some side effects and return
different values for the same initial conditions [#pure_function]_. An **iterator is
purely functional** if it meets the following criteria:
- ``gen`` function always returns the same values for the same ``param`` and
``state`` values (idempotence property)
- ``param`` and ``state`` values are not modified during the ``gen`` call and
a new ``state`` object is returned instead (referential transparency
property).
Pure functional iterators are very important for us. Pure functional iterator
can be safety cloned or reapplied without creating side effects. Many library
function use these properties.
Finite iterators
````````````````
Iterators can be **finite** (sooner or later end up) or **infinite**
(never end).
Since there is no way to determine automatically if an iterator is finite or
not [#turing]_ the library function can not automatically resolve infinite
loops. It is your obligation not to pass infinite iterators to reducing
functions.
Tracing JIT
-----------
Tracing just-in-time compilation is a technique used by virtual machines to
optimize the execution of a program at runtime. This is done by recording a
linear sequence of frequently executed operations, compiling them to native
machine code and executing them.
First profiling information for loops is collected. After a hot loop has been
identified, a special tracing mode is entered which records all executed
operations of that loop. This sequence of operations is called a **trace**.
The trace is then optimized and compiled to machine code. When this
loop is executed again the compiled trace is called instead of the program
counterpart [#tracing_jit]_.
Why is tracing JIT important for us? The LuaJIT tracing compiler can detect
tail-, up- and down-recursion [#luajit-recursion]_, unroll compositions of
functions and inline high-order functions [#luajit-optimizations]_.
All of these concepts make the foundation for functional programming.
.. [#iterators] http://en.wikipedia.org/wiki/Turtles_all_the_way_down
.. [#lua_for] http://www.lua.org/manual/5.2/manual.html#3.3.5
.. [#pure_function] http://en.wikipedia.org/wiki/Pure_function
.. [#turing] `Proved by Turing <http://en.wikipedia.org/wiki/Halting_problem>`_
.. [#tracing_jit] http://en.wikipedia.org/wiki/Tracing_just-in-time_compilation
.. [#luajit-recursion] http://lambda-the-ultimate.org/node/3851#comment-57679
.. [#luajit-optimizations] http://wiki.luajit.org/Optimizations
================================================
FILE: fun-scm-1.rockspec
================================================
package = "fun"
version = "scm-1"
source = {
url = "git+https://github.com/luafun/luafun.git",
}
description = {
summary = "High-performance functional programming library for Lua",
homepage = "https://luafun.github.io/",
license = "MIT/X11",
maintainer = "Roman Tsisyk <roman@tarantool.org>",
detailed = [[
Lua Fun is a high-performance functional programming library for Lua
designed with LuaJIT's trace compiler in mind.
Lua Fun provides a set of more than 50 programming primitives typically
found in languages like Standard ML, Haskell, Erlang, JavaScript, Python and
even Lisp. High-order functions such as map, filter, reduce, zip, etc.,
make it easy to write simple and efficient functional code.
]]
}
dependencies = {
"lua"
}
build = {
type = "builtin",
modules = {
fun = "fun.lua",
},
copy_directories = { "tests" },
}
================================================
FILE: fun.lua
================================================
---
--- Lua Fun - a high-performance functional programming library for LuaJIT
---
--- Copyright (c) 2013-2017 Roman Tsisyk <roman@tsisyk.com>
---
--- Distributed under the MIT/X11 License. See COPYING.md for more details.
---
local exports = {}
local methods = {}
-- compatibility with Lua 5.1/5.2
local unpack = rawget(table, "unpack") or unpack
--------------------------------------------------------------------------------
-- Tools
--------------------------------------------------------------------------------
local return_if_not_empty = function(state_x, ...)
if state_x == nil then
return nil
end
return ...
end
local call_if_not_empty = function(fun, state_x, ...)
if state_x == nil then
return nil
end
return state_x, fun(...)
end
local function deepcopy(orig) -- used by cycle()
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
else
copy = orig
end
return copy
end
local iterator_mt = {
-- usually called by for-in loop
__call = function(self, param, state)
return self.gen(param, state)
end;
__tostring = function(self)
return '<generator>'
end;
-- add all exported methods
__index = methods;
}
local wrap = function(gen, param, state)
return setmetatable({
gen = gen,
param = param,
state = state
}, iterator_mt), param, state
end
exports.wrap = wrap
local unwrap = function(self)
return self.gen, self.param, self.state
end
methods.unwrap = unwrap
--------------------------------------------------------------------------------
-- Basic Functions
--------------------------------------------------------------------------------
local nil_gen = function(_param, _state)
return nil
end
local string_gen = function(param, state)
local state = state + 1
if state > #param then
return nil
end
local r = string.sub(param, state, state)
return state, r
end
local ipairs_gen = ipairs({}) -- get the generating function from ipairs
local pairs_gen = pairs({ a = 0 }) -- get the generating function from pairs
local map_gen = function(tab, key)
local value
local key, value = pairs_gen(tab, key)
return key, key, value
end
local rawiter = function(obj, param, state)
assert(obj ~= nil, "invalid iterator")
if type(obj) == "table" then
local mt = getmetatable(obj);
if mt ~= nil then
if mt == iterator_mt then
return obj.gen, obj.param, obj.state
elseif mt.__ipairs ~= nil then
return mt.__ipairs(obj)
elseif mt.__pairs ~= nil then
return mt.__pairs(obj)
end
end
if #obj > 0 then
-- array
return ipairs(obj)
else
-- hash
return map_gen, obj, nil
end
elseif (type(obj) == "function") then
return obj, param, state
elseif (type(obj) == "string") then
if #obj == 0 then
return nil_gen, nil, nil
end
return string_gen, obj, 0
end
error(string.format('object %s of type "%s" is not iterable',
obj, type(obj)))
end
local iter = function(obj, param, state)
return wrap(rawiter(obj, param, state))
end
exports.iter = iter
local method0 = function(fun)
return function(self)
return fun(self.gen, self.param, self.state)
end
end
local method1 = function(fun)
return function(self, arg1)
return fun(arg1, self.gen, self.param, self.state)
end
end
local method2 = function(fun)
return function(self, arg1, arg2)
return fun(arg1, arg2, self.gen, self.param, self.state)
end
end
local export0 = function(fun)
return function(gen, param, state)
return fun(rawiter(gen, param, state))
end
end
local export1 = function(fun)
return function(arg1, gen, param, state)
return fun(arg1, rawiter(gen, param, state))
end
end
local export2 = function(fun)
return function(arg1, arg2, gen, param, state)
return fun(arg1, arg2, rawiter(gen, param, state))
end
end
local each = function(fun, gen, param, state)
repeat
state = call_if_not_empty(fun, gen(param, state))
until state == nil
end
methods.each = method1(each)
exports.each = export1(each)
methods.for_each = methods.each
exports.for_each = exports.each
methods.foreach = methods.each
exports.foreach = exports.each
--------------------------------------------------------------------------------
-- Generators
--------------------------------------------------------------------------------
local range_gen = function(param, state)
local stop, step = param[1], param[2]
local state = state + step
if state > stop then
return nil
end
return state, state
end
local range_rev_gen = function(param, state)
local stop, step = param[1], param[2]
local state = state + step
if state < stop then
return nil
end
return state, state
end
local range = function(start, stop, step)
if step == nil then
if stop == nil then
if start == 0 then
return nil_gen, nil, nil
end
stop = start
start = stop > 0 and 1 or -1
end
step = start <= stop and 1 or -1
end
assert(type(start) == "number", "start must be a number")
assert(type(stop) == "number", "stop must be a number")
assert(type(step) == "number", "step must be a number")
assert(step ~= 0, "step must not be zero")
if (step > 0) then
return wrap(range_gen, {stop, step}, start - step)
elseif (step < 0) then
return wrap(range_rev_gen, {stop, step}, start - step)
end
end
exports.range = range
local duplicate_table_gen = function(param_x, state_x)
return state_x + 1, unpack(param_x)
end
local duplicate_fun_gen = function(param_x, state_x)
return state_x + 1, param_x(state_x)
end
local duplicate_gen = function(param_x, state_x)
return state_x + 1, param_x
end
local duplicate = function(...)
if select('#', ...) <= 1 then
return wrap(duplicate_gen, select(1, ...), 0)
else
return wrap(duplicate_table_gen, {...}, 0)
end
end
exports.duplicate = duplicate
exports.replicate = duplicate
exports.xrepeat = duplicate
local tabulate = function(fun)
assert(type(fun) == "function")
return wrap(duplicate_fun_gen, fun, 0)
end
exports.tabulate = tabulate
local zeros = function()
return wrap(duplicate_gen, 0, 0)
end
exports.zeros = zeros
local ones = function()
return wrap(duplicate_gen, 1, 0)
end
exports.ones = ones
local rands_gen = function(param_x, _state_x)
return 0, math.random(param_x[1], param_x[2])
end
local rands_nil_gen = function(_param_x, _state_x)
return 0, math.random()
end
local rands = function(n, m)
if n == nil and m == nil then
return wrap(rands_nil_gen, 0, 0)
end
assert(type(n) == "number", "invalid first arg to rands")
if m == nil then
m = n
n = 0
else
assert(type(m) == "number", "invalid second arg to rands")
end
assert(n < m, "empty interval")
return wrap(rands_gen, {n, m - 1}, 0)
end
exports.rands = rands
--------------------------------------------------------------------------------
-- Slicing
--------------------------------------------------------------------------------
local nth = function(n, gen_x, param_x, state_x)
assert(n > 0, "invalid first argument to nth")
-- An optimization for arrays and strings
if gen_x == ipairs_gen then
return param_x[state_x + n]
elseif gen_x == string_gen then
if state_x + n <= #param_x then
return string.sub(param_x, state_x + n, state_x + n)
else
return nil
end
end
for i=1,n-1,1 do
state_x = gen_x(param_x, state_x)
if state_x == nil then
return nil
end
end
return return_if_not_empty(gen_x(param_x, state_x))
end
methods.nth = method1(nth)
exports.nth = export1(nth)
local head_call = function(state, ...)
if state == nil then
error("head: iterator is empty")
end
return ...
end
local head = function(gen, param, state)
return head_call(gen(param, state))
end
methods.head = method0(head)
exports.head = export0(head)
exports.car = exports.head
methods.car = methods.head
local tail = function(gen, param, state)
state = gen(param, state)
if state == nil then
return wrap(nil_gen, nil, nil)
end
return wrap(gen, param, state)
end
methods.tail = method0(tail)
exports.tail = export0(tail)
exports.cdr = exports.tail
methods.cdr = methods.tail
local take_n_gen_x = function(i, state_x, ...)
if state_x == nil then
return nil
end
return {i, state_x}, ...
end
local take_n_gen = function(param, state)
local n, gen_x, param_x = param[1], param[2], param[3]
local i, state_x = state[1], state[2]
if i >= n then
return nil
end
return take_n_gen_x(i + 1, gen_x(param_x, state_x))
end
local take_n = function(n, gen, param, state)
assert(n >= 0, "invalid first argument to take_n")
return wrap(take_n_gen, {n, gen, param}, {0, state})
end
methods.take_n = method1(take_n)
exports.take_n = export1(take_n)
local take_while_gen_x = function(fun, state_x, ...)
if state_x == nil or not fun(...) then
return nil
end
return state_x, ...
end
local take_while_gen = function(param, state_x)
local fun, gen_x, param_x = param[1], param[2], param[3]
return take_while_gen_x(fun, gen_x(param_x, state_x))
end
local take_while = function(fun, gen, param, state)
assert(type(fun) == "function", "invalid first argument to take_while")
return wrap(take_while_gen, {fun, gen, param}, state)
end
methods.take_while = method1(take_while)
exports.take_while = export1(take_while)
local take = function(n_or_fun, gen, param, state)
if type(n_or_fun) == "number" then
return take_n(n_or_fun, gen, param, state)
else
return take_while(n_or_fun, gen, param, state)
end
end
methods.take = method1(take)
exports.take = export1(take)
local drop_n = function(n, gen, param, state)
assert(n >= 0, "invalid first argument to drop_n")
local i
for i=1,n,1 do
state = gen(param, state)
if state == nil then
return wrap(nil_gen, nil, nil)
end
end
return wrap(gen, param, state)
end
methods.drop_n = method1(drop_n)
exports.drop_n = export1(drop_n)
-- Unpack values from param[3] on the first iteration, then return
-- values from the provided iterator.
--
-- A generator function for drop_while().
local drop_while_gen = function(param, state)
local results = param[3]
if not results then
return param[1](param[2], state)
else
param[3] = nil
return state, unpack(results, 1, table.maxn(results))
end
end
-- Checks if drop_while should continue skipping. If iterator is not exhausted
-- and skipping is over, elements returned by iterator are wrapped into a table
-- and returned as the second return value. Note that a table is created only
-- once, on the last iteration, for the sake of performance.
local drop_while_x = function(fun, state_x, ...)
if state_x ~= nil and not fun(...) then
return state_x, {...}
end
return state_x
end
local drop_while = function(fun, gen_x, param_x, state_x)
assert(type(fun) == "function", "invalid first argument to drop_while")
local pivot = nil
while state_x ~= nil and pivot == nil do
state_x, pivot = drop_while_x(fun, gen_x(param_x, state_x))
end
if state_x == nil then
return wrap(nil_gen, nil, nil)
end
return wrap(drop_while_gen, {gen_x, param_x, pivot}, state_x)
end
methods.drop_while = method1(drop_while)
exports.drop_while = export1(drop_while)
local drop = function(n_or_fun, gen_x, param_x, state_x)
if type(n_or_fun) == "number" then
return drop_n(n_or_fun, gen_x, param_x, state_x)
else
return drop_while(n_or_fun, gen_x, param_x, state_x)
end
end
methods.drop = method1(drop)
exports.drop = export1(drop)
local split = function(n_or_fun, gen_x, param_x, state_x)
return take(n_or_fun, gen_x, param_x, state_x),
drop(n_or_fun, gen_x, param_x, state_x)
end
methods.split = method1(split)
exports.split = export1(split)
methods.split_at = methods.split
exports.split_at = exports.split
methods.span = methods.split
exports.span = exports.split
--------------------------------------------------------------------------------
-- Indexing
--------------------------------------------------------------------------------
local index = function(x, gen, param, state)
local i = 1
for _k, r in gen, param, state do
if r == x then
return i
end
i = i + 1
end
return nil
end
methods.index = method1(index)
exports.index = export1(index)
methods.index_of = methods.index
exports.index_of = exports.index
methods.elem_index = methods.index
exports.elem_index = exports.index
local indexes_gen = function(param, state)
local x, gen_x, param_x = param[1], param[2], param[3]
local i, state_x = state[1], state[2]
local r
while true do
state_x, r = gen_x(param_x, state_x)
if state_x == nil then
return nil
end
i = i + 1
if r == x then
return {i, state_x}, i
end
end
end
local indexes = function(x, gen, param, state)
return wrap(indexes_gen, {x, gen, param}, {0, state})
end
methods.indexes = method1(indexes)
exports.indexes = export1(indexes)
methods.elem_indexes = methods.indexes
exports.elem_indexes = exports.indexes
methods.indices = methods.indexes
exports.indices = exports.indexes
methods.elem_indices = methods.indexes
exports.elem_indices = exports.indexes
--------------------------------------------------------------------------------
-- Filtering
--------------------------------------------------------------------------------
local filter1_gen = function(fun, gen_x, param_x, state_x, a)
while true do
if state_x == nil or fun(a) then break; end
state_x, a = gen_x(param_x, state_x)
end
return state_x, a
end
-- call each other
local filterm_gen
local filterm_gen_shrink = function(fun, gen_x, param_x, state_x)
return filterm_gen(fun, gen_x, param_x, gen_x(param_x, state_x))
end
filterm_gen = function(fun, gen_x, param_x, state_x, ...)
if state_x == nil then
return nil
end
if fun(...) then
return state_x, ...
end
return filterm_gen_shrink(fun, gen_x, param_x, state_x)
end
local filter_detect = function(fun, gen_x, param_x, state_x, ...)
if select('#', ...) < 2 then
return filter1_gen(fun, gen_x, param_x, state_x, ...)
else
return filterm_gen(fun, gen_x, param_x, state_x, ...)
end
end
local filter_gen = function(param, state_x)
local fun, gen_x, param_x = param[1], param[2], param[3]
return filter_detect(fun, gen_x, param_x, gen_x(param_x, state_x))
end
local filter = function(fun, gen, param, state)
return wrap(filter_gen, {fun, gen, param}, state)
end
methods.filter = method1(filter)
exports.filter = export1(filter)
methods.remove_if = methods.filter
exports.remove_if = exports.filter
local grep = function(fun_or_regexp, gen, param, state)
local fun = fun_or_regexp
if type(fun_or_regexp) == "string" then
fun = function(x) return string.find(x, fun_or_regexp) ~= nil end
end
return filter(fun, gen, param, state)
end
methods.grep = method1(grep)
exports.grep = export1(grep)
local partition = function(fun, gen, param, state)
local neg_fun = function(...)
return not fun(...)
end
return filter(fun, gen, param, state),
filter(neg_fun, gen, param, state)
end
methods.partition = method1(partition)
exports.partition = export1(partition)
--------------------------------------------------------------------------------
-- Reducing
--------------------------------------------------------------------------------
local foldl_call = function(fun, start, state, ...)
if state == nil then
return nil, start
end
return state, fun(start, ...)
end
local foldl = function(fun, start, gen_x, param_x, state_x)
while true do
state_x, start = foldl_call(fun, start, gen_x(param_x, state_x))
if state_x == nil then
break;
end
end
return start
end
methods.foldl = method2(foldl)
exports.foldl = export2(foldl)
methods.reduce = methods.foldl
exports.reduce = exports.foldl
local length = function(gen, param, state)
if gen == ipairs_gen or gen == string_gen then
return #param - state
end
local len = 0
repeat
state = gen(param, state)
len = len + 1
until state == nil
return len - 1
end
methods.length = method0(length)
exports.length = export0(length)
local is_null = function(gen, param, state)
return gen(param, deepcopy(state)) == nil
end
methods.is_null = method0(is_null)
exports.is_null = export0(is_null)
local is_prefix_of = function(iter_x, iter_y)
local gen_x, param_x, state_x = iter(iter_x)
local gen_y, param_y, state_y = iter(iter_y)
local r_x, r_y
repeat
state_x, r_x = gen_x(param_x, state_x)
if state_x == nil then
return true
end
state_y, r_y = gen_y(param_y, state_y)
until state_y == nil or r_x ~= r_y
return false
end
methods.is_prefix_of = is_prefix_of
exports.is_prefix_of = is_prefix_of
local all = function(fun, gen_x, param_x, state_x)
local r
repeat
state_x, r = call_if_not_empty(fun, gen_x(param_x, state_x))
until state_x == nil or not r
return state_x == nil
end
methods.all = method1(all)
exports.all = export1(all)
methods.every = methods.all
exports.every = exports.all
local any = function(fun, gen_x, param_x, state_x)
local r
repeat
state_x, r = call_if_not_empty(fun, gen_x(param_x, state_x))
until state_x == nil or r
return not not r
end
methods.any = method1(any)
exports.any = export1(any)
methods.some = methods.any
exports.some = exports.any
local sum = function(gen, param, state)
local s = 0
local r = 0
repeat
s = s + r
state, r = gen(param, state)
until state == nil
return s
end
methods.sum = method0(sum)
exports.sum = export0(sum)
local product = function(gen, param, state)
local p = 1
local r = 1
repeat
p = p * r
state, r = gen(param, state)
until state == nil
return p
end
methods.product = method0(product)
exports.product = export0(product)
local min_cmp = function(m, n)
if n < m then return n else return m end
end
local max_cmp = function(m, n)
if n > m then return n else return m end
end
local min = function(gen, param, state)
local state, m = gen(param, state)
if state == nil then
error("min: iterator is empty")
end
local cmp
if type(m) == "number" then
-- An optimization: use math.min for numbers
cmp = math.min
else
cmp = min_cmp
end
for _, r in gen, param, state do
m = cmp(m, r)
end
return m
end
methods.min = method0(min)
exports.min = export0(min)
methods.minimum = methods.min
exports.minimum = exports.min
local min_by = function(cmp, gen_x, param_x, state_x)
local state_x, m = gen_x(param_x, state_x)
if state_x == nil then
error("min: iterator is empty")
end
for _, r in gen_x, param_x, state_x do
m = cmp(m, r)
end
return m
end
methods.min_by = method1(min_by)
exports.min_by = export1(min_by)
methods.minimum_by = methods.min_by
exports.minimum_by = exports.min_by
local max = function(gen_x, param_x, state_x)
local state_x, m = gen_x(param_x, state_x)
if state_x == nil then
error("max: iterator is empty")
end
local cmp
if type(m) == "number" then
-- An optimization: use math.max for numbers
cmp = math.max
else
cmp = max_cmp
end
for _, r in gen_x, param_x, state_x do
m = cmp(m, r)
end
return m
end
methods.max = method0(max)
exports.max = export0(max)
methods.maximum = methods.max
exports.maximum = exports.max
local max_by = function(cmp, gen_x, param_x, state_x)
local state_x, m = gen_x(param_x, state_x)
if state_x == nil then
error("max: iterator is empty")
end
for _, r in gen_x, param_x, state_x do
m = cmp(m, r)
end
return m
end
methods.max_by = method1(max_by)
exports.max_by = export1(max_by)
methods.maximum_by = methods.max_by
exports.maximum_by = exports.max_by
local totable = function(gen_x, param_x, state_x)
local tab, key, val = {}
while true do
state_x, val = gen_x(param_x, state_x)
if state_x == nil then
break
end
table.insert(tab, val)
end
return tab
end
methods.totable = method0(totable)
exports.totable = export0(totable)
local tomap = function(gen_x, param_x, state_x)
local tab, key, val = {}
while true do
state_x, key, val = gen_x(param_x, state_x)
if state_x == nil then
break
end
tab[key] = val
end
return tab
end
methods.tomap = method0(tomap)
exports.tomap = export0(tomap)
--------------------------------------------------------------------------------
-- Transformations
--------------------------------------------------------------------------------
local map_gen = function(param, state)
local gen_x, param_x, fun = param[1], param[2], param[3]
return call_if_not_empty(fun, gen_x(param_x, state))
end
local map = function(fun, gen, param, state)
return wrap(map_gen, {gen, param, fun}, state)
end
methods.map = method1(map)
exports.map = export1(map)
local enumerate_gen_call = function(state, i, state_x, ...)
if state_x == nil then
return nil
end
return {i + 1, state_x}, i, ...
end
local enumerate_gen = function(param, state)
local gen_x, param_x = param[1], param[2]
local i, state_x = state[1], state[2]
return enumerate_gen_call(state, i, gen_x(param_x, state_x))
end
local enumerate = function(gen, param, state)
return wrap(enumerate_gen, {gen, param}, {1, state})
end
methods.enumerate = method0(enumerate)
exports.enumerate = export0(enumerate)
local intersperse_call = function(i, state_x, ...)
if state_x == nil then
return nil
end
return {i + 1, state_x}, ...
end
local intersperse_gen = function(param, state)
local x, gen_x, param_x = param[1], param[2], param[3]
local i, state_x = state[1], state[2]
if i % 2 == 1 then
return {i + 1, state_x}, x
else
return intersperse_call(i, gen_x(param_x, state_x))
end
end
-- TODO: interperse must not add x to the tail
local intersperse = function(x, gen, param, state)
return wrap(intersperse_gen, {x, gen, param}, {0, state})
end
methods.intersperse = method1(intersperse)
exports.intersperse = export1(intersperse)
--------------------------------------------------------------------------------
-- Compositions
--------------------------------------------------------------------------------
local function zip_gen_r(param, state, state_new, ...)
if #state_new == #param / 2 then
return state_new, ...
end
local i = #state_new + 1
local gen_x, param_x = param[2 * i - 1], param[2 * i]
local state_x, r = gen_x(param_x, state[i])
if state_x == nil then
return nil
end
table.insert(state_new, state_x)
return zip_gen_r(param, state, state_new, r, ...)
end
local zip_gen = function(param, state)
return zip_gen_r(param, state, {})
end
-- A special hack for zip/chain to skip last two state, if a wrapped iterator
-- has been passed
local numargs = function(...)
local n = select('#', ...)
if n >= 3 then
-- Fix last argument
local it = select(n - 2, ...)
if type(it) == 'table' and getmetatable(it) == iterator_mt and
it.param == select(n - 1, ...) and it.state == select(n, ...) then
return n - 2
end
end
return n
end
local zip = function(...)
local n = numargs(...)
if n == 0 then
return wrap(nil_gen, nil, nil)
end
local param = { [2 * n] = 0 }
local state = { [n] = 0 }
local i, gen_x, param_x, state_x
for i=1,n,1 do
local it = select(n - i + 1, ...)
gen_x, param_x, state_x = rawiter(it)
param[2 * i - 1] = gen_x
param[2 * i] = param_x
state[i] = state_x
end
return wrap(zip_gen, param, state)
end
methods.zip = zip
exports.zip = zip
local cycle_gen_call = function(param, state_x, ...)
if state_x == nil then
local gen_x, param_x, state_x0 = param[1], param[2], param[3]
return gen_x(param_x, deepcopy(state_x0))
end
return state_x, ...
end
local cycle_gen = function(param, state_x)
local gen_x, param_x, state_x0 = param[1], param[2], param[3]
return cycle_gen_call(param, gen_x(param_x, state_x))
end
local cycle = function(gen, param, state)
return wrap(cycle_gen, {gen, param, state}, deepcopy(state))
end
methods.cycle = method0(cycle)
exports.cycle = export0(cycle)
-- call each other
local chain_gen_r1
local chain_gen_r2 = function(param, state, state_x, ...)
if state_x == nil then
local i = state[1]
i = i + 1
if param[3 * i - 2] == nil then
return nil
end
local state_x = param[3 * i]
return chain_gen_r1(param, {i, state_x})
end
return {state[1], state_x}, ...
end
chain_gen_r1 = function(param, state)
local i, state_x = state[1], state[2]
local gen_x, param_x = param[3 * i - 2], param[3 * i - 1]
return chain_gen_r2(param, state, gen_x(param_x, state[2]))
end
local chain = function(...)
local n = numargs(...)
if n == 0 then
return wrap(nil_gen, nil, nil)
end
local param = { [3 * n] = 0 }
local i, gen_x, param_x, state_x
for i=1,n,1 do
local elem = select(i, ...)
gen_x, param_x, state_x = iter(elem)
param[3 * i - 2] = gen_x
param[3 * i - 1] = param_x
param[3 * i] = state_x
end
return wrap(chain_gen_r1, param, {1, param[3]})
end
methods.chain = chain
exports.chain = chain
--------------------------------------------------------------------------------
-- Operators
--------------------------------------------------------------------------------
local operator = {
----------------------------------------------------------------------------
-- Comparison operators
----------------------------------------------------------------------------
lt = function(a, b) return a < b end,
le = function(a, b) return a <= b end,
eq = function(a, b) return a == b end,
ne = function(a, b) return a ~= b end,
ge = function(a, b) return a >= b end,
gt = function(a, b) return a > b end,
----------------------------------------------------------------------------
-- Arithmetic operators
----------------------------------------------------------------------------
add = function(a, b) return a + b end,
div = function(a, b) return a / b end,
floordiv = function(a, b) return math.floor(a/b) end,
intdiv = function(a, b)
local q = a / b
if a >= 0 then return math.floor(q) else return math.ceil(q) end
end,
mod = function(a, b) return a % b end,
mul = function(a, b) return a * b end,
neq = function(a) return -a end,
unm = function(a) return -a end, -- an alias
pow = function(a, b) return a ^ b end,
sub = function(a, b) return a - b end,
truediv = function(a, b) return a / b end,
----------------------------------------------------------------------------
-- String operators
----------------------------------------------------------------------------
concat = function(a, b) return a..b end,
len = function(a) return #a end,
length = function(a) return #a end, -- an alias
----------------------------------------------------------------------------
-- Logical operators
----------------------------------------------------------------------------
land = function(a, b) return a and b end,
lor = function(a, b) return a or b end,
lnot = function(a) return not a end,
truth = function(a) return not not a end,
}
exports.operator = operator
methods.operator = operator
exports.op = operator
methods.op = operator
--------------------------------------------------------------------------------
-- module definitions
--------------------------------------------------------------------------------
-- a special syntax sugar to export all functions to the global table
setmetatable(exports, {
__call = function(t, override)
for k, v in pairs(t) do
if rawget(_G, k) ~= nil then
local msg = 'function ' .. k .. ' already exists in global scope.'
if override then
rawset(_G, k, v)
print('WARNING: ' .. msg .. ' Overwritten.')
else
print('NOTICE: ' .. msg .. ' Skipped.')
end
else
rawset(_G, k, v)
end
end
end,
})
return exports
================================================
FILE: rpm/lua-fun.spec
================================================
%define luaver 5.3
%define luapkgdir %{_datadir}/lua/%{luaver}
# LuaJIT is compatible with Lua 5.1 and uses the same directory for modules
%global ljpkgdir %{_datadir}/lua/5.1
Name: lua-fun
Version: 0.1.3
Release: 1%{?dist}
Summary: Functional programming library for Lua
Group: Development/Libraries
License: MIT
URL: https://github.com/luafun/luafun
Source0: https://github.com/luafun/luafun/archive/%{version}/luafun-%{version}.tar.gz
BuildArch: noarch
BuildRequires: luajit >= 2.0
BuildRequires: lua >= 5.1
Requires: lua >= 5.1
%package -n luajit-fun
Summary: Functional programming library for LuaJIT
Requires: luajit >= 2.0
%description -n lua-fun
Lua Fun is a high-performance functional programming library for Lua
designed with LuaJIT's trace compiler in mind.
Lua Fun provides a set of more than 50 programming primitives typically
found in languages like Standard ML, Haskell, Erlang, JavaScript, Python and
even Lisp. High-order functions such as map, filter, reduce, zip, etc.,
make it easy to write simple and efficient functional code.
This package provides a module for Lua %{luaver}.
%description -n luajit-fun
Lua Fun is a high-performance functional programming library for Lua
designed with LuaJIT's trace compiler in mind.
Lua Fun provides a set of more than 50 programming primitives typically
found in languages like Standard ML, Haskell, Erlang, JavaScript, Python and
even Lisp. High-order functions such as map, filter, reduce, zip, etc.,
make it easy to write simple and efficient functional code.
This package provides a module for LuaJIT.
%prep
%setup -q -n luafun-%{version}
%build
# nothing to do
%install
# Install for Lua
mkdir -p %{buildroot}%{luapkgdir}
cp -av fun.lua %{buildroot}%{luapkgdir}/fun.lua
# Install for LuaJIT
mkdir -p %{buildroot}%{ljpkgdir}
cp -av fun.lua %{buildroot}%{ljpkgdir}/fun.lua
%check
cd tests
luajit ./runtest *.lua
lua ./runtest *.lua
%files -n lua-fun
%{luapkgdir}/fun.lua
%doc README.md CONTRIBUTING.md
%license COPYING.md
%files -n luajit-fun
%{ljpkgdir}/fun.lua
%doc README.md CONTRIBUTING.md
%license COPYING.md
%changelog
* Mon Jan 18 2016 Roman Tsisyk <roman@tarantool.org> - 0.1.3-1
- Initial version.
================================================
FILE: tests/.gitignore
================================================
*.new
================================================
FILE: tests/basic.lua
================================================
--------------------------------------------------------------------------------
-- iter
--------------------------------------------------------------------------------
--
-- Arrays
--
for _it, a in iter({1, 2, 3}) do print(a) end
--[[test
1
2
3
--test]]
for _it, a in iter(iter(iter({1, 2, 3}))) do print(a) end
--[[test
1
2
3
--test]]
for _it, a in wrap(wrap(iter({1, 2, 3}))) do print(a) end
--[[test
1
2
3
--test]]
for _it, a in wrap(wrap(ipairs({1, 2, 3}))) do print(a) end
--[[test
1
2
3
--test]]
for _it, a in iter({}) do print(a) end
--[[test
--test]]
for _it, a in iter(iter(iter({}))) do print(a) end
--[[test
--test]]
for _it, a in wrap(wrap(iter({}))) do print(a) end
--[[test
--test]]
for _it, a in wrap(wrap(ipairs({}))) do print(a) end
--[[test
--test]]
-- Check that ``iter`` for arrays is equivalent to ``ipairs``
local t = {1, 2, 3}
gen1, param1, state1 = iter(t):unwrap()
gen2, param2, state2 = ipairs(t)
print(gen1 == gen2, param1 == param2, state1 == state2)
--[[test
true true true
--test]]
-- Test that ``wrap`` do nothing for wrapped iterators
gen1, param1, state1 = iter({1, 2, 3})
gen2, param2, state2 = wrap(gen1, param1, state1):unwrap()
print(gen1 == gen2, param1 == param2, state1 == state2)
--[[test
true true true
--test]]
--
-- Maps
--
local t = {}
for _it, k, v in iter({ a = 1, b = 2, c = 3}) do t[#t + 1] = k end
table.sort(t)
for _it, v in iter(t) do print(v) end
--[[test
a
b
c
--test]]
local t = {}
for _it, k, v in iter(iter(iter({ a = 1, b = 2, c = 3}))) do t[#t + 1] = k end
table.sort(t)
for _it, v in iter(t) do print(v) end
--[[test
a
b
c
--test]]
for _it, k, v in iter({}) do print(k, v) end
--[[test
--test]]
for _it, k, v in iter(iter(iter({}))) do print(k, v) end
--[[test
--test]]
--
-- String
--
for _it, a in iter("abcde") do print(a) end
--[[test
a
b
c
d
e
--test]]
for _it, a in iter(iter(iter("abcde"))) do print(a) end
--[[test
a
b
c
d
e
--test]]
for _it, a in iter("") do print(a) end
--[[test
--test]]
for _it, a in iter(iter(iter(""))) do print(a) end
--[[test
--test]]
--
-- Custom generators
--
local function mypairs_gen(max, state)
if (state >= max) then
return nil
end
return state + 1, state + 1
end
local function mypairs(max)
return mypairs_gen, max, 0
end
for _it, a in iter(mypairs(10)) do print(a) end
--[[test
1
2
3
4
5
6
7
8
9
10
--test]]
--
-- Invalid values
--
for _it, a in iter(1) do print(a) end
--[[test
error: object 1 of type "number" is not iterable
--test]]
for _it, a in iter(1, 2, 3, 4, 5, 6, 7) do print(a) end
--[[test
error: object 1 of type "number" is not iterable
--test]]
--------------------------------------------------------------------------------
-- each
--------------------------------------------------------------------------------
each(print, {1, 2, 3})
--[[test
1
2
3
--test]]
each(print, iter({1, 2, 3}))
--[[test
1
2
3
--test]]
each(print, {})
--[[test
--test]]
each(print, iter({}))
--[[test
--test]]
local keys, vals = {}, {}
each(function(k, v)
keys[#keys + 1] = k
vals[#vals + 1] = v
end, { a = 1, b = 2, c = 3})
table.sort(keys)
table.sort(vals)
each(print, keys)
each(print, vals)
--[[test
a
b
c
1
2
3
--test]]
each(print, "abc")
--[[test
a
b
c
--test]]
each(print, iter("abc"))
--[[test
a
b
c
--test]]
print(for_each == each) -- an alias
--[[test
true
--test]]
print(foreach == each) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- totable
--------------------------------------------------------------------------------
local tab = totable(range(5))
print(type(tab), #tab)
each(print, tab)
--[[test
table 5
1
2
3
4
5
--test]]
local tab = totable(range(0))
print(type(tab), #tab)
--[[test
table 0
--test]]
local tab = totable("abcdef")
print(type(tab), #tab)
each(print, tab)
--[[test
table 6
a
b
c
d
e
f
--test]]
local unpack = rawget(table, "unpack") or unpack
local tab = totable({ 'a', {'b', 'c'}, {'d', 'e', 'f'}})
print(type(tab), #tab)
each(print, tab[1])
each(print, map(unpack, drop(1, tab)))
--[[test
table 3
a
b c
d e f
--test]]
--------------------------------------------------------------------------------
-- tomap
--------------------------------------------------------------------------------
local tab = tomap(zip(range(1, 7), 'abcdef'))
print(type(tab), #tab)
each(print, iter(tab))
--[[test
table 6
a
b
c
d
e
f
--test]]
local tab = tomap({a = 1, b = 2, c = 3})
print(type(tab), #tab)
local t = {}
for _it, k, v in iter(tab) do t[v] = k end
table.sort(t)
for k, v in ipairs(t) do print(k, v) end
--[[test
table 0
1 a
2 b
3 c
--test]]
local tab = tomap(enumerate("abcdef"))
print(type(tab), #tab)
each(print, tab)
--[[test
table 6
a
b
c
d
e
f
--test]]
================================================
FILE: tests/compositions.lua
================================================
--------------------------------------------------------------------------------
-- zip
--------------------------------------------------------------------------------
dump(zip({"a", "b", "c", "d"}, {"one", "two", "three"}))
--[[test
a one
b two
c three
--test]]
dump(zip())
--[[test
--test]]
dump(zip(range(0)))
--[[test
error: invalid iterator
--test]]
dump(zip(range(0), range(0)))
--[[test
error: invalid iterator
--test]]
print(nth(10, zip(range(1, 100, 3), range(1, 100, 5), range(1, 100, 7))))
--[[test
28 46 64
--test]]
dump(zip(partition(function(x) return x > 7 end, range(1, 15, 1))))
--[[test
8 1
9 2
10 3
11 4
12 5
13 6
14 7
--test]]
--------------------------------------------------------------------------------
-- cycle
--------------------------------------------------------------------------------
dump(take(15, cycle({"a", "b", "c", "d", "e"})))
--[[test
a
b
c
d
e
a
b
c
d
e
a
b
c
d
e
--test]]
dump(take(15, cycle(range(5))))
--[[test
1
2
3
4
5
1
2
3
4
5
1
2
3
4
5
--test]]
dump(take(15, cycle(zip(range(5), {"a", "b", "c", "d", "e"}))))
--[[test
1 a
2 b
3 c
4 d
5 e
1 a
2 b
3 c
4 d
5 e
1 a
2 b
3 c
4 d
5 e
--test]]
--------------------------------------------------------------------------------
-- chain
--------------------------------------------------------------------------------
dump(chain(range(2)))
--[[test
1
2
--test]]
dump(chain(range(2), {"a", "b", "c"}, {"one", "two", "three"}))
--[[test
1
2
a
b
c
one
two
three
--test]]
dump(take(15, cycle(chain(enumerate({"a", "b", "c"}),
{"one", "two", "three"}))))
--[[test
1 a
2 b
3 c
one
two
three
1 a
2 b
3 c
one
two
three
1 a
2 b
3 c
--test]]
local tab = {}
local keys = {}
for _it, k, v in chain({ a = 11, b = 12, c = 13}, { d = 21, e = 22 }) do
tab[k] = v
table.insert(keys, k)
end
table.sort(keys)
for _, key in ipairs(keys) do print(key, tab[key]) end
--[[test
a 11
b 12
c 13
d 21
e 22
--test]]
dump(chain(range(0), range(0), range(0)))
--[[test
error: invalid iterator
--test]]
dump(chain(range(0), range(1), range(0)))
--[[test
error: invalid iterator
--test]]
-- Similar to fun.range(), but accepts just 'stop' for simplicity.
--
-- The key point of this iterator generator is that it doesn't use
-- 'param' (the second value in the gen-param-state triplet) and
-- pass nil to it. This is needed to reproduce the next scenario.
function myrange(stop)
local function gen(_param, i)
if i < stop then
return i + 1, i + 1
end
return nil
end
return wrap(gen, nil, 0)
end
-- gh-86: verify that chain don't stop on an iterator that uses
-- param = nil.
dump(chain(myrange(3), myrange(3), myrange(3)))
--[[test
1
2
3
1
2
3
1
2
3
--test]]
================================================
FILE: tests/filtering.lua
================================================
--------------------------------------------------------------------------------
-- filter
--------------------------------------------------------------------------------
dump(filter(function(x) return x % 3 == 0 end, range(10)))
--[[test
3
6
9
--test]]
dump(filter(function(x) return x % 3 == 0 end, range(0)))
--[[test
--test]]
dump(take(5, filter(function(i, x) return i % 3 == 0 end,
enumerate(duplicate('x')))))
--[[test
3 x
6 x
9 x
12 x
15 x
--test]]
function filter_fun(a, b, c)
if a % 16 == 0 then
return true
else
return false
end
end
function test3(a, b, c)
return a, c, b
end
n = 50
dump(filter(filter_fun, map(test3, zip(range(0, n, 1),
range(0, n, 2), range(0, n, 3)))))
--[[test
0 0 0
16 48 32
--test]]
print(remove_if == filter) -- an alias
--[[test
true
--test]]
dump_state(filter(function(x) return x % 2 == 0 end, ipairs({0, 1, 1, 2, 3, 4})))
--[[test
1
4
6
--test]]
--------------------------------------------------------------------------------
-- grep
--------------------------------------------------------------------------------
lines_to_grep = {
[[Lorem ipsum dolor sit amet, consectetur adipisicing elit, ]],
[[sed do eiusmod tempor incididunt ut labore et dolore magna ]],
[[aliqua. Ut enim ad minim veniam, quis nostrud exercitation ]],
[[ullamco laboris nisi ut aliquip ex ea commodo consequat.]],
[[Duis aute irure dolor in reprehenderit in voluptate velit ]],
[[esse cillum dolore eu fugiat nulla pariatur. Excepteur sint ]],
[[occaecat cupidatat non proident, sunt in culpa qui officia ]],
[[deserunt mollit anim id est laborum.]]
}
dump(grep("lab", lines_to_grep))
--[[test
sed do eiusmod tempor incididunt ut labore et dolore magna
ullamco laboris nisi ut aliquip ex ea commodo consequat.
deserunt mollit anim id est laborum.
--test]]
lines_to_grep = {
[[Emily]],
[[Chloe]],
[[Megan]],
[[Jessica]],
[[Emma]],
[[Sarah]],
[[Elizabeth]],
[[Sophie]],
[[Olivia]],
[[Lauren]]
}
dump(grep("^Em", lines_to_grep))
--[[test
Emily
Emma
--test]]
dump_state(grep("^Em", lines_to_grep))
--[[test
1
5
--test]]
--------------------------------------------------------------------------------
-- partition
--------------------------------------------------------------------------------
dump(zip(partition(function(i, x) return i % 3 == 0 end, range(10))))
--[[test
3 1
6 2
9 4
--test]]
================================================
FILE: tests/generators.lua
================================================
--------------------------------------------------------------------------------
-- range
--------------------------------------------------------------------------------
dump(range(0))
print('--')
for i=1,0 do print(i) end
--[[test
--
--test]]
dump(range(0, 0))
print('--')
for i=0,0 do print(i) end
--[[test
0
--
0
--test]]
dump(range(5))
print('--')
for i=1,5 do print(i) end
--[[test
1
2
3
4
5
--
1
2
3
4
5
--test]]
dump(range(0, 5))
print('--')
for i=0,5 do print(i) end
--[[test
0
1
2
3
4
5
--
0
1
2
3
4
5
--test]]
dump(range(0, 5, 1))
print('--')
for i=0,5,1 do print(i) end
--[[test
0
1
2
3
4
5
--
0
1
2
3
4
5
--test]]
dump(range(0, 10, 2))
print('--')
for i=0,10,2 do print(i) end
--[[test
0
2
4
6
8
10
--
0
2
4
6
8
10
--test]]
dump(range(-5))
print('--')
for i=-1,-5,-1 do print(i) end
--[[test
-1
-2
-3
-4
-5
--
-1
-2
-3
-4
-5
--test]]
dump(range(0, -5, 1))
print('--')
for i=0,-5,1 do print(i) end
--[[test
--
--test]]
dump(range(0, -5, -1))
print('--')
for i=0,-5,-1 do print(i) end
--[[test
0
-1
-2
-3
-4
-5
--
0
-1
-2
-3
-4
-5
--test]]
dump(range(0, -10, -2))
print('--')
for i=0,-10,-2 do print(i) end
--[[test
0
-2
-4
-6
-8
-10
--
0
-2
-4
-6
-8
-10
--test]]
dump(range(1.2, 1.6, 0.1))
--[[test
1.2
1.3
1.4
1.5
--test]]
-- Invalid step
dump(range(0, 5, 0))
--[[test
error: step must not be zero
--test]]
--------------------------------------------------------------------------------
-- duplicate
--------------------------------------------------------------------------------
dump(take(5, duplicate(48)))
--[[test
48
48
48
48
48
--test]]
dump(take(5, duplicate(1,2,3,4,5)))
--[[test
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
--test]]
print(xrepeat == duplicate) -- an alias
--[[test
true
--test]]
print(replicate == duplicate) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- tabulate
--------------------------------------------------------------------------------
dump(take(5, tabulate(function(x) return 2 * x end)))
--[[test
0
2
4
6
8
--test]]
--------------------------------------------------------------------------------
-- zeros
--------------------------------------------------------------------------------
dump(take(5, zeros()))
--[[test
0
0
0
0
0
--test]]
--------------------------------------------------------------------------------
-- ones
--------------------------------------------------------------------------------
dump(take(5, ones()))
--[[test
1
1
1
1
1
--test]]
--------------------------------------------------------------------------------
-- rands
--------------------------------------------------------------------------------
print(all(function(x) return x >= 0 and x < 1 end, take(5, rands())))
--[[test
true
--test]]
dump(take(5, rands(0)))
--[[test
error: empty interval
--test]]
print(all(function(x) return math.floor(x) == x end, take(5, rands(10))))
--[[test
true
--test]]
print(all(function(x) return math.floor(x) == x end, take(5, rands(1024))))
--[[test
true
--test]]
dump(take(5, rands(0, 1)))
--[[test
0
0
0
0
0
--test]]
dump(take(5, rands(5, 6)))
--[[test
5
5
5
5
5
--test]]
print(all(function(x) return x >= 10 and x < 20 end, take(20, rands(10, 20))))
--[[test
true
--test]]
================================================
FILE: tests/indexing.lua
================================================
--------------------------------------------------------------------------------
-- index
--------------------------------------------------------------------------------
print(index(2, range(5)))
--[[test
2
--test]]
print(index(10, range(5)))
--[[test
nil
--test]]
print(index(2, range(0)))
--[[test
nil
--test]]
print(index("b", {"a", "b", "c", "d", "e"}))
--[[test
2
--test]]
print(index(1, enumerate({"a", "b", "c", "d", "e"})))
--[[test
1
--test]]
print(index("b", "abcdef"))
--[[test
2
--test]]
print(index_of == index) -- an alias
--[[test
true
--test]]
print(elem_index == index) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- indexes
--------------------------------------------------------------------------------
dump(indexes("a", {"a", "b", "c", "d", "e", "a", "b", "c", "d", "a", "a"}))
--[[test
1
6
10
11
--test]]
dump(indexes("f", {"a", "b", "c", "d", "e", "a", "b", "c", "d", "a", "a"}))
--[[test
--test]]
dump(indexes("f", {}))
--[[test
--test]]
dump(indexes(1, enumerate({"a", "b", "c", "d", "e"})))
--[[test
1
--test]]
print(indices == indexes) -- an alias
--[[test
true
--test]]
print(elem_indexes == indexes) -- an alias
--[[test
true
--test]]
print(elem_indices == indexes) -- an alias
--[[test
true
--test]]
================================================
FILE: tests/operators.lua
================================================
--
-- All these functions are fully covered by Lua tests.
-- This test just checks that all functions were defined correctly.
--
print(op == operator) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- Comparison operators
--------------------------------------------------------------------------------
local comparators = { 'le', 'lt', 'eq', 'ne', 'ge', 'gt' }
for _k, op in iter(comparators) do
print('op', op)
print('==')
print('num:')
print(operator[op](0, 1))
print(operator[op](1, 0))
print(operator[op](0, 0))
print('str:')
print(operator[op]("abc", "cde"))
print(operator[op]("cde", "abc"))
print(operator[op]("abc", "abc"))
print('')
end
--[[test
op le
==
num:
true
false
true
str:
true
false
true
op lt
==
num:
true
false
false
str:
true
false
false
op eq
==
num:
false
false
true
str:
false
false
true
op ne
==
num:
true
true
false
str:
true
true
false
op ge
==
num:
false
true
true
str:
false
true
true
op gt
==
num:
false
true
false
str:
false
true
false
--test]]
--------------------------------------------------------------------------------
-- Arithmetic operators
--------------------------------------------------------------------------------
print(operator.add(-1.0, 1.0))
print(operator.add(0, 0))
print(operator.add(12, 2))
--[[test
0
0
14
--test]]
print(operator.div(10, 2))
print(operator.div(10, 3))
print(operator.div(-10, 3))
--[[test
5
3.3333333333333
-3.3333333333333
--test]]
print(operator.floordiv(10, 3))
print(operator.floordiv(11, 3))
print(operator.floordiv(12, 3))
print(operator.floordiv(-10, 3))
print(operator.floordiv(-11, 3))
print(operator.floordiv(-12, 3))
--[[test
3
3
4
-4
-4
-4
--test]]
print(operator.intdiv(10, 3))
print(operator.intdiv(11, 3))
print(operator.intdiv(12, 3))
print(operator.intdiv(-10, 3))
print(operator.intdiv(-11, 3))
print(operator.intdiv(-12, 3))
--[[test
3
3
4
-3
-3
-4
--test]]
print(operator.truediv(10, 3))
print(operator.truediv(11, 3))
print(operator.truediv(12, 3))
print(operator.truediv(-10, 3))
print(operator.truediv(-11, 3))
print(operator.truediv(-12, 3))
--[[test
3.3333333333333
3.6666666666667
4
-3.3333333333333
-3.6666666666667
-4
--test]]
print(operator.mod(10, 2))
print(operator.mod(10, 3))
print(operator.mod(-10, 3))
--[[test
0
1
2
--test]]
print(operator.mul(10, 0.1))
print(operator.mul(0, 0))
print(operator.mul(-1, -1))
--[[test
1
0
1
--test]]
print(operator.neq(1))
print(operator.neq(0) == 0)
print(operator.neq(-0) == 0)
print(operator.neq(-1))
--[[test
-1
true
true
1
--test]]
print(operator.unm(1))
print(operator.unm(0) == 0)
print(operator.unm(-0) == 0)
print(operator.unm(-1))
--[[test
-1
true
true
1
--test]]
print(operator.pow(2, 3))
print(operator.pow(0, 10))
print(operator.pow(2, 0))
--[[test
8
0
1
--test]]
print(operator.sub(2, 3))
print(operator.sub(0, 10))
print(operator.sub(2, 2))
--[[test
-1
-10
0
--test]]
--------------------------------------------------------------------------------
-- String operators
--------------------------------------------------------------------------------
print(operator.concat("aa", "bb"))
print(operator.concat("aa", ""))
print(operator.concat("", "bb"))
--[[test
aabb
aa
bb
--test]]
print(operator.len(""))
print(operator.len("ab"))
print(operator.len("abcd"))
--[[test
0
2
4
--test]]
print(operator.length(""))
print(operator.length("ab"))
print(operator.length("abcd"))
--[[test
0
2
4
--test]]
----------------------------------------------------------------------------
-- Logical operators
----------------------------------------------------------------------------
print(operator.land(true, true))
print(operator.land(true, false))
print(operator.land(false, true))
print(operator.land(false, false))
print(operator.land(1, 0))
print(operator.land(0, 1))
print(operator.land(1, 1))
print(operator.land(0, 0))
--[[test
true
false
false
false
0
1
1
0
--test]]
print(operator.lor(true, true))
print(operator.lor(true, false))
print(operator.lor(false, true))
print(operator.lor(false, false))
print(operator.lor(1, 0))
print(operator.lor(0, 1))
print(operator.lor(1, 1))
print(operator.lor(0, 0))
--[[test
true
true
true
false
1
0
1
0
--test]]
print(operator.lnot(true))
print(operator.lnot(false))
print(operator.lor(1))
print(operator.lor(0))
--[[test
false
true
1
0
--test]]
print(operator.truth(true))
print(operator.truth(false))
print(operator.truth(1))
print(operator.truth(0))
print(operator.truth(nil))
print(operator.truth(""))
print(operator.truth({}))
--[[test
true
false
true
true
false
true
true
--test]]
================================================
FILE: tests/reducing.lua
================================================
--------------------------------------------------------------------------------
-- foldl
--------------------------------------------------------------------------------
print(foldl(function(acc, x) return acc + x end, 0, range(5)))
--[[test
15
--test]]
print(foldl(operator.add, 0, range(5)))
--[[test
15
--test]]
print(foldl(function(acc, x, y) return acc + x * y; end, 0,
zip(range(1, 5), {4, 3, 2, 1})))
--[[test
20
--test]]
print(reduce == foldl) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- length
--------------------------------------------------------------------------------
print(length({"a", "b", "c", "d", "e"}))
--[[test
5
--test]]
print(length({}))
--[[test
0
--test]]
print(length(range(0)))
--[[test
0
--test]]
-- gh-55: consider input iterator state in array/string optimizations
print(length(drop_n(3, {"a", "b", "c", "d", "e"})))
--[[test
2
--test]]
-- gh-55: consider input iterator state in array/string optimizations
print(length(drop_n(3, "abcdef")))
--[[test
3
--test]]
--------------------------------------------------------------------------------
-- is_null
--------------------------------------------------------------------------------
print(is_null({"a", "b", "c", "d", "e"}))
--[[test
false
--test]]
print(is_null({}))
--[[test
true
--test]]
print(is_null(range(0)))
--[[test
true
--test]]
local gen, init, state = range(5)
print(is_null(gen, init, state))
dump(gen, init, state)
--[[test
false
1
2
3
4
5
--test]]
--------------------------------------------------------------------------------
-- is_prefix_of
--------------------------------------------------------------------------------
print(is_prefix_of({"a"}, {"a", "b", "c"}))
--[[test
true
--test]]
print(is_prefix_of({}, {"a", "b", "c"}))
--[[test
true
--test]]
print(is_prefix_of({}, {}))
--[[test
true
--test]]
print(is_prefix_of({"a"}, {}))
--[[test
false
--test]]
print(is_prefix_of({"a"}, {"b"}))
--[[test
false
--test]]
print(is_prefix_of({"a", "b"}, {"a", "c"}))
--[[test
false
--test]]
print(is_prefix_of({"a", "b", "c"}, {"a", "b"}))
--[[test
false
--test]]
print(is_prefix_of(range(5), range(6)))
--[[test
true
--test]]
print(is_prefix_of(range(6), range(5)))
--[[test
false
--test]]
print(is_prefix_of(range(15), range(15)))
--[[test
true
--test]]
print(is_prefix_of(range(21), range(20)))
--[[test
false
--test]]
print(is_prefix_of(range(15), range(20)))
--[[test
true
--test]]
print(is_prefix_of(
range(3):map(function(v) return v < 3 and v or nil end), -- {1, 2, nil}
{ 1, 2 }
))
--[[test
false
--test]]
local function range_once(stop)
local v = 0
return function()
v = v + 1
if v <= stop then
return v, v
end
return nil
end
end
local prefix = range_once(2) -- null expected
local iterator = range_once(3) -- trimmed prefix expected
print(is_prefix_of(wrap(prefix), wrap(iterator)))
print(is_null(prefix))
print((iterator()))
--[[test
true
true
3
--test]]
--------------------------------------------------------------------------------
-- all
--------------------------------------------------------------------------------
print(all(function(x) return x end, {true, true, true, true}))
--[[test
true
--test]]
print(all(function(x) return x end, {true, true, true, false}))
--[[test
false
--test]]
print(all(function(x) return x end, {}))
--[[test
true
--test]]
print(every == all) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- any
--------------------------------------------------------------------------------
print(any(function(x) return x end, {false, false, false, false}))
--[[test
false
--test]]
print(any(function(x) return x end, {false, false, false, true}))
--[[test
true
--test]]
print(any(function(x) return x end, {}))
--[[test
false
--test]]
print(some == any) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- sum
--------------------------------------------------------------------------------
print(sum(range(1, 5)))
--[[test
15
--test]]
print(sum(range(1, 5, 0.5)))
--[[test
27
--test]]
print(sum(range(0)))
--[[test
0
--test]]
--------------------------------------------------------------------------------
-- product
--------------------------------------------------------------------------------
print(product(range(1, 5)))
--[[test
120
--test]]
print(product(range(1, 5, 0.5)))
--[[test
7087.5
--test]]
print(product(range(0)))
--[[test
1
--test]]
--------------------------------------------------------------------------------
-- min
--------------------------------------------------------------------------------
print(min(range(1, 10, 1)))
--[[test
1
--test]]
print(min({"f", "d", "c", "d", "e"}))
--[[test
c
--test]]
print(min({}))
--[[test
error: min: iterator is empty
--test]]
print(minimum == min) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- min_by
--------------------------------------------------------------------------------
function min_cmp(a, b) if -a < -b then return a else return b end end
--[[test
--test]]
print(min_by(min_cmp, range(1, 10, 1)))
--[[test
10
--test]]
print(min_by(min_cmp, {}))
--[[test
error: min: iterator is empty
--test]]
print(minimum_by == min_by) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- max
--------------------------------------------------------------------------------
print(max(range(1, 10, 1)))
--[[test
10
--test]]
print(max({"f", "d", "c", "d", "e"}))
--[[test
f
--test]]
print(max({}))
--[[test
error: max: iterator is empty
--test]]
print(maximum == max) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- max_by
--------------------------------------------------------------------------------
function max_cmp(a, b) if -a > -b then return a else return b end end
--[[test
--test]]
print(max_by(max_cmp, range(1, 10, 1)))
--[[test
1
--test]]
print(max_by(max_cmp, {}))
--[[test
error: max: iterator is empty
--test]]
print(maximum_by == max_by) -- an alias
--[[test
true
--test]]
================================================
FILE: tests/runtest
================================================
#!/usr/bin/env lua
package.path = "../?.lua;"..package.path
require "fun" ()
function dump(gen, init, state) each(print, gen, init, state) end
-- Check if state is preserved
function dump_state(gen, init, state)
for s in gen, init, state do
print(s)
end
end
local unpack = rawget(table, "unpack") or unpack
local loadstring = rawget(_G, "loadstring") or load
function file_print(file, ...)
local n, i = select("#",...)
for i=1,n do
local x = select(i, ...)
if type(x) == "number" and math.floor(x) == math.ceil(x) then
-- A special hack for Lua 5.3: remove .0 for integer
x = string.match(select(i,...), '^-?%d+')
end
file:write(tostring(x))
if i~=n then
file:write(' ')
end
end
file:write('\n')
end
local globals = {}
setmetatable(_G, {
__newindex = function(t,k,v)
local info = debug.getinfo(2, "S")
if info.short_src:sub(1,7) ~= '[string' then
local file = info.short_src
local func = debug.getinfo(2, "n").name or ""
local line = info.linedefined
globals[file..':'..line..':'..k] = {file, line, func, k}
end
rawset(t, k, v)
end
})
local function process(test_name)
io.write("Testing ", test_name, "\n")
local new_name = test_name..".new"
local test_file = io.open(test_name, 'r')
local content = test_file:read("*a");
test_file:close()
local new_file = io.open(new_name, 'w')
local prev_print = print
print = function(...) file_print(new_file, ...) end
io.flush()
local expr
for expr in content:gmatch("(.-)%s*--%[%[test.-test%]%]") do
new_file:write(expr)
new_file:write("\n--[[test\n")
local res, err = loadstring(expr)
if res then
res, err = pcall(res, expr)
end
if not res then
new_file:write('error: ', err:match(".-:%d+:%s*(.*)"), "\n")
end
new_file:write("--test]]")
end
new_file:write("\n")
new_file:close()
print = prev_print
local r = os.execute(string.format('diff -U4 "%s" "%s" 2>&1',
test_name, new_name))
if r then
os.remove(new_name)
return true
else
return false
end
end
if #arg <= 0 then
io.write("Usage: runtest *.lua", "\n")
os.exit(1)
end
local failed, i = {}
for i=1,#arg,1 do
local test_name = arg[i]
if not process(test_name) then
table.insert(failed, test_name)
end
end
if #failed > 0 then
io.write("\n")
io.write("Failed tests:", "\n")
for _k,test_name in ipairs(failed) do
io.write(" ", test_name, "\n")
end
io.write("\n", "Please review *.new files and update tests", "\n")
end
if next(globals) then
io.write("\n")
io.write("Some global variables have been declared by mistake:", "\n")
for k, pollution in pairs(globals) do
local file, line, func, var = unpack(pollution)
io.write(file..":"..line.." function "..func.."() = var '"..var.."'", "\n")
end
io.write("\n", "Please declare them with the local statement", "\n")
elseif #failed == 0 then
io.write("All tests have passed!", "\n")
os.exit(0)
end
================================================
FILE: tests/slicing.lua
================================================
--------------------------------------------------------------------------------
-- nth
--------------------------------------------------------------------------------
print(nth(2, range(5)))
--[[test
2
--test]]
print(nth(10, range(5)))
--[[test
nil
--test]]
print(nth(2, range(0)))
--[[test
nil
--test]]
print(nth(2, {"a", "b", "c", "d", "e"}))
--[[test
b
--test]]
print(nth(2, enumerate({"a", "b", "c", "d", "e"})))
--[[test
2 b
--test]]
print(nth(1, "abcdef"))
--[[test
a
--test]]
print(nth(2, "abcdef"))
--[[test
b
--test]]
print(nth(6, "abcdef"))
--[[test
f
--test]]
print(nth(0, "abcdef"))
--[[test
error: invalid first argument to nth
--test]]
print(nth(7, "abcdef"))
--[[test
nil
--test]]
-- gh-55: consider input iterator state in array/string optimizations
print(nth(1, drop_n(3, {"a", "b", "c", "d", "e"})))
--[[test
d
--test]]
-- gh-55: consider input iterator state in array/string optimizations
print(nth(1, drop_n(3, "abcdef")))
--[[test
d
--test]]
--------------------------------------------------------------------------------
-- head
--------------------------------------------------------------------------------
print(head({"a", "b", "c", "d", "e"}))
--[[test
a
--test]]
print(head({}))
--[[test
error: head: iterator is empty
--test]]
print(head(range(0)))
--[[test
error: head: iterator is empty
--test]]
print(head(enumerate({"a", "b"})))
--[[test
1 a
--test]]
print(car == head) -- an alias
--[[test
true
--test]]
--------------------------------------------------------------------------------
-- tail
--------------------------------------------------------------------------------
dump(tail({"a", "b", "c", "d", "e"}))
--[[test
b
c
d
e
--test]]
dump(tail({}))
--[[test
--test]]
dump(tail(range(0)))
--[[test
--test]]
dump(tail(enumerate({"a", "b"})))
--[[test
2 b
--test]]
print(cdr == tail) -- an alias
--[[test
true
--test]]
dump_state(tail(ipairs({"a", "b", "c", "d", "e"})))
--[[test
2
3
4
5
--test]]
--------------------------------------------------------------------------------
-- take_n
--------------------------------------------------------------------------------
dump(take_n(0, duplicate(48)))
--[[test
--test]]
dump(take_n(5, range(0)))
--[[test
--test]]
dump(take_n(1, duplicate(48)))
--[[test
48
--test]]
dump(take_n(5, duplicate(48)))
--[[test
48
48
48
48
48
--test]]
dump(take_n(5, enumerate(duplicate('x'))))
--[[test
1 x
2 x
3 x
4 x
5 x
--test]]
--------------------------------------------------------------------------------
-- take_while
--------------------------------------------------------------------------------
dump(take_while(function(x) return x < 5 end, range(10)))
--[[test
1
2
3
4
--test]]
dump(take_while(function(x) return x < 5 end, range(0)))
--[[test
--test]]
dump(take_while(function(x) return x > 100 end, range(10)))
--[[test
--test]]
dump(take_while(function(i, a) return i ~=a end, enumerate({5, 2, 1, 3, 4})))
--[[test
1 5
--test]]
dump_state(take_while(function(x) return x < 3 end, ipairs({0, 0, 0, 2, 3})))
--[[test
1
2
3
4
--test]]
--------------------------------------------------------------------------------
-- take
--------------------------------------------------------------------------------
dump(take(function(x) return x < 5 end, range(10)))
--[[test
1
2
3
4
--test]]
dump(take(5, duplicate(48)))
--[[test
48
48
48
48
48
--test]]
--------------------------------------------------------------------------------
-- drop_n
--------------------------------------------------------------------------------
dump(drop_n(5, range(10)))
--[[test
6
7
8
9
10
--test]]
dump(drop_n(0, range(5)))
--[[test
1
2
3
4
5
--test]]
dump(drop_n(5, range(0)))
--[[test
--test]]
dump(drop_n(2, enumerate({'a', 'b', 'c', 'd', 'e'})))
--[[test
3 c
4 d
5 e
--test]]
dump_state(drop_n(3, ipairs({'a', 'b', 'c', 'd', 'e'})))
--[[test
4
5
--test]]
--------------------------------------------------------------------------------
-- drop_while
--------------------------------------------------------------------------------
dump(drop_while(function(x) return x < 5 end, range(10)))
--[[test
5
6
7
8
9
10
--test]]
dump(drop_while(function(x) return x < 5 end, range(0)))
--[[test
--test]]
dump(drop_while(function(x) return x > 100 end, range(10)))
--[[test
1
2
3
4
5
6
7
8
9
10
--test]]
dump(drop_while(function(i, a) return i ~=a end, enumerate({5, 2, 1, 3, 4})))
--[[test
2 2
3 1
4 3
5 4
--test]]
dump(drop_while(function(i, a) return i ~=a end,
zip({1, 2, 3, 4, 5}, {5, 4, 3, 2, 1})))
--[[test
3 3
4 2
5 1
--test]]
dump_state(drop_while(function(x) return x < 5 end, ipairs({0, 0, 4, 6, 2})))
--[[test
4
5
--test]]
--------------------------------------------------------------------------------
-- drop
--------------------------------------------------------------------------------
dump(drop(5, range(10)))
--[[test
6
7
8
9
10
--test]]
dump(drop(function(x) return x < 5 end, range(10)))
--[[test
5
6
7
8
9
10
--test]]
--------------------------------------------------------------------------------
-- span
--------------------------------------------------------------------------------
dump(zip(span(function(x) return x < 5 end, range(10))))
--[[test
1 5
2 6
3 7
4 8
--test]]
dump(zip(span(5, range(10))))
--[[test
1 6
2 7
3 8
4 9
5 10
--test]]
dump(zip(span(function(x) return x < 5 end, range(0))))
--[[test
--test]]
dump(zip(span(function(x) return x < 5 end, range(5))))
--[[test
1 5
--test]]
print(split == span) -- an alias
--[[test
true
--test]]
print(split_at == span) -- an alias
--[[test
true
--test]]
================================================
FILE: tests/stateful.lua
================================================
-- compatibility with Lua 5.1/5.2
local unpack = rawget(table, "unpack") or unpack
function gen_stateful_iter(values)
local i = 1
local gen = function(_param, values)
if i > #values then
return nil
end
local t = values[i]
i = i + 1
if type(t) == 'table' then
return values, unpack(t, 1, table.maxn(t))
else
return values, t
end
end
return iter(gen, nil, values)
end
--------------------------------------------------------------------------------
-- drop_while
--------------------------------------------------------------------------------
-- Simple test
dump(gen_stateful_iter({1, 2, 3, 4, 5, 6, 5, 4, 3}):drop_while(function(x) return x < 5 end))
--[[test
5
6
5
4
3
--test]]
-- Multireturn
dump(gen_stateful_iter({{1, 10}, {3, 30}, {5, 50}, {6, 60}, {3, 20}}):drop_while(function(x) return x < 5 end))
--[[test
5 50
6 60
3 20
--test]]
-- Multireturn with nil
dump(gen_stateful_iter({{1, nil, 10}, {3, nil, 30}, {5, nil, 50}, {6, nil, 60}, {3, nil, 20}}):drop_while(function(x) return x < 5 end))
--[[test
5 nil 50
6 nil 60
3 nil 20
--test]]
-- Multireturn with condition on second returned value
dump(gen_stateful_iter({{0, 1}, {0, 3}, {0, 5}, {0, 4}}):drop_while(function(x, y) return y < 5 end))
--[[test
0 5
0 4
--test]]
-- Empty iterator
dump(gen_stateful_iter({}):drop_while(function(x) return x < 5 end))
--[[test
--test]]
-- Always false
dump(gen_stateful_iter({1, 2, 3, 4, 5}):drop_while(function(x) return x > 100 end))
--[[test
1
2
3
4
5
--test]]
================================================
FILE: tests/transformations.lua
================================================
--------------------------------------------------------------------------------
-- map
--------------------------------------------------------------------------------
fun = function(...) return 'map', ... end
dump(map(fun, range(0)))
--[[test
--test]]
dump(map(fun, range(4)))
--[[test
map 1
map 2
map 3
map 4
--test]]
dump(map(fun, enumerate({"a", "b", "c", "d", "e"})))
--[[test
map 1 a
map 2 b
map 3 c
map 4 d
map 5 e
--test]]
dump(map(function(x) return 2 * x end, range(4)))
--[[test
2
4
6
8
--test]]
fun = nil
--[[test
--test]]
--------------------------------------------------------------------------------
-- enumerate
--------------------------------------------------------------------------------
dump(enumerate({"a", "b", "c", "d", "e"}))
--[[test
1 a
2 b
3 c
4 d
5 e
--test]]
dump(enumerate(enumerate(enumerate({"a", "b", "c", "d", "e"}))))
--[[test
1 1 1 a
2 2 2 b
3 3 3 c
4 4 4 d
5 5 5 e
--test]]
dump(enumerate(zip({"one", "two", "three", "four", "five"},
{"a", "b", "c", "d", "e"})))
--[[test
1 one a
2 two b
3 three c
4 four d
5 five e
--test]]
--------------------------------------------------------------------------------
-- intersperse
--------------------------------------------------------------------------------
dump(intersperse("x", {}))
dump(intersperse("x", {"a", "b", "c", "d", "e"}))
--[[test
a
x
b
x
c
x
d
x
e
x
--test]]
dump(intersperse("x", {"a", "b", "c", "d", "e", "f"}))
--[[test
a
x
b
x
c
x
d
x
e
x
f
x
--test]]
gitextract_diaikeab/
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── COPYING.md
├── HACKING.md
├── README.md
├── debian/
│ ├── .gitignore
│ ├── changelog
│ ├── compat
│ ├── control
│ ├── copyright
│ ├── lua-fun.docs
│ ├── lua5.1.dh-lua.conf
│ ├── patches/
│ │ └── series
│ ├── rules
│ ├── source/
│ │ └── format
│ └── watch
├── doc/
│ ├── .gitignore
│ ├── Makefile
│ ├── _static/
│ │ └── .keep
│ ├── _templates/
│ │ └── layout.html
│ ├── about.rst
│ ├── basic.rst
│ ├── compositions.rst
│ ├── conf.py
│ ├── filtering.rst
│ ├── generators.rst
│ ├── getting_started.rst
│ ├── index.rst
│ ├── indexing.rst
│ ├── intro.rst
│ ├── operators.rst
│ ├── reducing.rst
│ ├── reference.rst
│ ├── slicing.rst
│ ├── transformations.rst
│ └── under_the_hood.rst
├── fun-scm-1.rockspec
├── fun.lua
├── rpm/
│ └── lua-fun.spec
└── tests/
├── .gitignore
├── basic.lua
├── compositions.lua
├── filtering.lua
├── generators.lua
├── indexing.lua
├── operators.lua
├── reducing.lua
├── runtest
├── slicing.lua
├── stateful.lua
└── transformations.lua
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (145K chars).
[
{
"path": ".gitignore",
"chars": 28,
"preview": "*~\ntemp/\nfun.lua.c\n5.?-fun/\n"
},
{
"path": ".travis.yml",
"chars": 1744,
"preview": "sudo: false\nlanguage: C\nservices:\n - docker\n\nenv:\n global:\n - PRODUCT=lua-fun\n matrix:\n - OS=el DIST=7\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 478,
"preview": "Contributing\n============\n\nWe'd love for you to contribute to the project and make **Lua Fun** even better\nthan it is to"
},
{
"path": "COPYING.md",
"chars": 1292,
"preview": "Copying\n=======\n\n**Lua Fun** source codes, logo and documentation are distributed under the\n**[MIT/X11 License]** - same"
},
{
"path": "HACKING.md",
"chars": 204,
"preview": "# Hacking\n\n## Build documentation\n\nBuild and open in a Web browser:\n\n```sh\n$ cd doc\n$ make html\n$ xdg-open _build/html/i"
},
{
"path": "README.md",
"chars": 3742,
"preview": "Lua Functional\n==============\n\n<img src=\"/doc/logo.png\" align=\"right\" width=\"174px\" height=\"144px\" />\n\n**Lua Fun** is a "
},
{
"path": "debian/.gitignore",
"chars": 67,
"preview": "lua-fun/\ntmp/\ntrash\nfiles\nlua_versions\n*.install\n*.substvars\n*.log\n"
},
{
"path": "debian/changelog",
"chars": 156,
"preview": "lua-fun (0.1.3-1) unstable; urgency=medium\n\n * Initial release. (Closes: #811482)\n\n -- Roman Tsisyk <roman@tarantool.or"
},
{
"path": "debian/compat",
"chars": 2,
"preview": "9\n"
},
{
"path": "debian/control",
"chars": 784,
"preview": "Source: lua-fun\nSection: interpreters\nPriority: optional\nMaintainer: Roman Tsisyk <roman@tarantool.org>\nBuild-Depends: d"
},
{
"path": "debian/copyright",
"chars": 1597,
"preview": "Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: luafun\nUpstream-Contact: Roman "
},
{
"path": "debian/lua-fun.docs",
"chars": 10,
"preview": "README.md\n"
},
{
"path": "debian/lua5.1.dh-lua.conf",
"chars": 84,
"preview": "PKG_NAME=fun\nLUA_MODNAME=fun\nLUA_SOURCES=fun.lua\nLUA_TEST=tests/runtest tests/*.lua\n"
},
{
"path": "debian/patches/series",
"chars": 0,
"preview": ""
},
{
"path": "debian/rules",
"chars": 357,
"preview": "#!/usr/bin/make -f\n\nVERSION := $(shell dpkg-parsechangelog|grep ^Version|awk '{print $$2}')\nUVERSION := $(shell echo $("
},
{
"path": "debian/source/format",
"chars": 12,
"preview": "3.0 (quilt)\n"
},
{
"path": "debian/watch",
"chars": 291,
"preview": "# test this watch file using:\n# uscan --watchfile debian/watch --upstream-version 0.0.1 --package lua-fun\n# https://wiki"
},
{
"path": "doc/.gitignore",
"chars": 7,
"preview": "_build\n"
},
{
"path": "doc/Makefile",
"chars": 634,
"preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the "
},
{
"path": "doc/_static/.keep",
"chars": 0,
"preview": ""
},
{
"path": "doc/_templates/layout.html",
"chars": 511,
"preview": "{% extends \"!layout.html\" %}\n\n{% block footer %}\n{{ super() }}\n <script type=\"text/javascript\">\n(function(i,s,o,g,r,a"
},
{
"path": "doc/about.rst",
"chars": 1707,
"preview": "About\n=====\n\nCredits\n-------\n\nAn initial prototype was designed and enginered in one evening by Roman Tsisyk.\nAfter that"
},
{
"path": "doc/basic.rst",
"chars": 3579,
"preview": "Basic Functions\n===============\n\n.. currentmodule:: fun\n\nThe section contains functions to create iterators from Lua obj"
},
{
"path": "doc/compositions.rst",
"chars": 3154,
"preview": "Compositions\n============\n\n.. currentmodule:: fun\n\n.. function:: zip(...)\n iterator1:zip(iterator2, iterato"
},
{
"path": "doc/conf.py",
"chars": 2490,
"preview": "# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common op"
},
{
"path": "doc/filtering.rst",
"chars": 2953,
"preview": "Filtering\n=========\n\n.. currentmodule:: fun\n\nThis section contains functions to filter values during iteration.\n\n.. func"
},
{
"path": "doc/generators.rst",
"chars": 4943,
"preview": "Generators\n==========\n\n.. currentmodule:: fun\n\nThis section contains a number of useful generators modeled after Standar"
},
{
"path": "doc/getting_started.rst",
"chars": 5944,
"preview": "Getting Started\n===============\n\nPlease jump to `Using the Library`_ section if you are familiar with Lua and\nLuaJIT.\n\n."
},
{
"path": "doc/index.rst",
"chars": 378,
"preview": ".. _library-index:\n\n###############################\n Lua Functional Library\n###############################\n\n:Release: "
},
{
"path": "doc/indexing.rst",
"chars": 1653,
"preview": "Indexing\n========\n\n.. currentmodule:: fun\n\nThis section contains functions to find elements by its values.\n\n.. function:"
},
{
"path": "doc/intro.rst",
"chars": 2159,
"preview": "Introduction\n============\n\n.. currentmodule:: fun\n\n**Lua Fun** is a high-performance functional programming library\ndesi"
},
{
"path": "doc/operators.rst",
"chars": 3631,
"preview": "Operators\n=========\n\n.. module:: fun.operator\n\nThis auxiliary module exports a set of Lua operators as intrinsic functio"
},
{
"path": "doc/reducing.rst",
"chars": 7240,
"preview": "Reducing\n========\n\n.. currentmodule:: fun\n\nThe section contains functions to analyze iteration values and recombine\nthro"
},
{
"path": "doc/reference.rst",
"chars": 215,
"preview": "API Reference\n=============\n\n.. module:: fun\n\n.. toctree::\n\n basic.rst\n generators.rst\n slicing.rst\n indexing.rs"
},
{
"path": "doc/slicing.rst",
"chars": 5630,
"preview": "Slicing\n=======\n\n.. currentmodule:: fun\n\nThis section contains functions to make subsequences from iterators.\n\nBasic\n---"
},
{
"path": "doc/transformations.rst",
"chars": 1802,
"preview": "Transformations\n===============\n\n.. currentmodule:: fun\n\n.. function:: map(fun, gen, param, state)\n iterato"
},
{
"path": "doc/under_the_hood.rst",
"chars": 5793,
"preview": "Under the Hood\n==============\n\n.. currentmodule:: fun\n\nThe section sheds some light on the internal library structure an"
},
{
"path": "fun-scm-1.rockspec",
"chars": 886,
"preview": "package = \"fun\"\nversion = \"scm-1\"\n\nsource = {\n url = \"git+https://github.com/luafun/luafun.git\",\n}\n\ndescription = {\n "
},
{
"path": "fun.lua",
"chars": 29691,
"preview": "---\n--- Lua Fun - a high-performance functional programming library for LuaJIT\n---\n--- Copyright (c) 2013-2017 Roman Tsi"
},
{
"path": "rpm/lua-fun.spec",
"chars": 2187,
"preview": "%define luaver 5.3\n%define luapkgdir %{_datadir}/lua/%{luaver}\n# LuaJIT is compatible with Lua 5.1 and uses the same dir"
},
{
"path": "tests/.gitignore",
"chars": 6,
"preview": "*.new\n"
},
{
"path": "tests/basic.lua",
"chars": 4748,
"preview": "--------------------------------------------------------------------------------\n-- iter\n-------------------------------"
},
{
"path": "tests/compositions.lua",
"chars": 2697,
"preview": "--------------------------------------------------------------------------------\n-- zip\n--------------------------------"
},
{
"path": "tests/filtering.lua",
"chars": 2438,
"preview": "--------------------------------------------------------------------------------\n-- filter\n-----------------------------"
},
{
"path": "tests/generators.lua",
"chars": 3254,
"preview": "--------------------------------------------------------------------------------\n-- range\n------------------------------"
},
{
"path": "tests/indexing.lua",
"chars": 1319,
"preview": "--------------------------------------------------------------------------------\n-- index\n------------------------------"
},
{
"path": "tests/operators.lua",
"chars": 4619,
"preview": "--\n-- All these functions are fully covered by Lua tests.\n-- This test just checks that all functions were defined corre"
},
{
"path": "tests/reducing.lua",
"chars": 6350,
"preview": "--------------------------------------------------------------------------------\n-- foldl\n------------------------------"
},
{
"path": "tests/runtest",
"chars": 3250,
"preview": "#!/usr/bin/env lua\n\npackage.path = \"../?.lua;\"..package.path\nrequire \"fun\" ()\nfunction dump(gen, init, state) each(print"
},
{
"path": "tests/slicing.lua",
"chars": 5556,
"preview": "--------------------------------------------------------------------------------\n-- nth\n--------------------------------"
},
{
"path": "tests/stateful.lua",
"chars": 1577,
"preview": "-- compatibility with Lua 5.1/5.2\nlocal unpack = rawget(table, \"unpack\") or unpack\n\nfunction gen_stateful_iter(values)\n "
},
{
"path": "tests/transformations.lua",
"chars": 1474,
"preview": "--------------------------------------------------------------------------------\n-- map\n--------------------------------"
}
]
About this extraction
This page contains the full source code of the rtsisyk/luafun GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (132.2 KB), approximately 39.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.