Index: Modules/functionalmodule.c =================================================================== --- Modules/functionalmodule.c (revision 42109) +++ Modules/functionalmodule.c (working copy) @@ -5,6 +5,7 @@ /* Functional module written and maintained by Hye-Shik Chang with adaptations by Raymond Hettinger + foldl and foldr by Collin Winter Copyright (c) 2004, 2005 Python Software Foundation. All rights reserved. */ @@ -191,7 +192,7 @@ return 0; } -static PyGetSetDef partail_getsetlist[] = { +static PyGetSetDef partial_getsetlist[] = { {"__dict__", (getter)partial_get_dict, (setter)partial_set_dict}, {NULL} /* Sentinel */ }; @@ -229,7 +230,7 @@ 0, /* tp_iternext */ 0, /* tp_methods */ partial_memberlist, /* tp_members */ - partail_getsetlist, /* tp_getset */ + partial_getsetlist, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ @@ -241,13 +242,162 @@ PyObject_GC_Del, /* tp_free */ }; +/* foldl ********************************************************************/ +static PyObject * +functional_foldl(PyObject *self, PyObject *args) +{ + PyObject *func, *result, *list; + PyObject *iter, *item, *func_args, *func_return; + + if (PyTuple_GET_SIZE(args) != 3) { + PyErr_SetString(PyExc_TypeError, + "function 'foldl' takes exactly 3 arguments"); + return NULL; + } + + func = PyTuple_GET_ITEM(args, 0); + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "argument 1 to foldl() must be callable"); + return NULL; + } + + result = PyTuple_GET_ITEM(args, 1); + list = PyTuple_GET_ITEM(args, 2); + + iter = PyObject_GetIter(list); + if (iter == NULL) { + PyErr_SetString(PyExc_TypeError, + "argument 3 to foldl() must support iteration"); + return 0; + } + + Py_INCREF(result); + func_return = result; + + while (1) { + item = PyIter_Next(iter); + if (NULL == item) { + if (PyErr_Occurred()) { + goto Fail1; + } + break; + } + + func_args = PyTuple_New(2); + PyTuple_SET_ITEM(func_args, 0, func_return); + PyTuple_SET_ITEM(func_args, 1, item); + + func_return = PyEval_CallObject(func, func_args); + Py_DECREF(func_args); + if (NULL == func_return) { + goto Fail1; + } + } + + goto Succeed; + +Fail1: + Py_XDECREF(func_return); + func_return = NULL; +Succeed: + Py_DECREF(iter); + return func_return; +} + +PyDoc_STRVAR(foldl_doc, +"foldl(function, start, iterable) -> object\n\ +\n\ +Takes a binary function, a starting value (usually some kind of 'zero'), and\ +an iterable. The function is applied to the starting value and the first\ +element\ of the list, then the result of that and the second element of the\ +list, then the result of that and the third element of the list, and so on.\n\ +\n\ +foldl(add, 0, [1, 2, 3]) is equivalent to add(add(add(0, 1), 2), 3)"); + +/* foldr ********************************************************************/ + +static PyObject * +foldr_worker(PyObject *func, PyObject *base, PyObject *iter) +{ + PyObject *item, *func_args, *func_return, *recursive; + + item = PyIter_Next(iter); + if (NULL == item) { + if (PyErr_Occurred()) { + return NULL; + } + Py_INCREF(base); + return base; + } + + recursive = foldr_worker(func, base, iter); + if (NULL == recursive) { + Py_DECREF(base); + return NULL; + } + + func_args = PyTuple_New(2); + PyTuple_SET_ITEM(func_args, 0, item); + PyTuple_SET_ITEM(func_args, 1, recursive); + + func_return = PyEval_CallObject(func, func_args); + Py_DECREF(func_args); + return func_return; +} + +static PyObject * +functional_foldr(PyObject *self, PyObject *args) +{ + PyObject *func, *base, *list, *iter, *result; + + if (PyTuple_GET_SIZE(args) != 3) { + PyErr_SetString(PyExc_TypeError, + "function 'foldr' takes exactly 3 arguments"); + return NULL; + } + + func = PyTuple_GET_ITEM(args, 0); + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "argument 1 to foldr() must be callable"); + return NULL; + } + + base = PyTuple_GET_ITEM(args, 1); + list = PyTuple_GET_ITEM(args, 2); + + iter = PyObject_GetIter(list); + if (iter == NULL) { + PyErr_SetString(PyExc_TypeError, + "argument 3 to foldr() must support iteration"); + return NULL; + } + + result = foldr_worker(func, base, iter); + + Py_DECREF(iter); + return result; +} + +PyDoc_STRVAR(foldr_doc, +"foldr(function, start, iterable) -> object\n\ +\n\ +Like foldl, but starts from the end of the iterable and works back toward the\ +beginning. For example, foldr(subtract, 0, [1, 2, 3]) == 2, but\ +foldl(subtract, 0, [1, 2, 3] == -6\n\ +\n\ +foldr(add, 0, [1, 2, 3]) is equivalent to add(1, add(2, add(3, 0)))""); + /* module level code ********************************************************/ PyDoc_STRVAR(module_doc, "Tools for functional programming."); static PyMethodDef module_methods[] = { + {"foldl", functional_foldl, METH_VARARGS, foldl_doc}, + {"foldr", functional_foldr, METH_VARARGS, foldr_doc}, {NULL, NULL} /* sentinel */ }; Index: Lib/test/test_functional.py =================================================================== --- Lib/test/test_functional.py (revision 42109) +++ Lib/test/test_functional.py (working copy) @@ -152,14 +152,84 @@ thetype = PythonPartial +def add(a, b): + return a + b + +def minus(a, b): + return a - b + +def fail(a, b): + raise RuntimeError +from functional import foldl +class Test_foldl(unittest.TestCase): + fold_func = foldl + minus_answer = -6 + def test_bad_arg_count(self): + try: + self.fold_func() + except TypeError, e: + pass + else: + self.fail("Failed to raise TypeError") + + def test_bad_first_arg(self): + try: + self.fold_func(5, 0, [1, 2, 3]) + except TypeError, e: + pass + else: + self.fail("Failed to raise TypeError") + + def test_bad_third_arg(self): + def add(a, b): return a + b + + try: + self.fold_func(add, 0, 123) + except TypeError, e: + pass + else: + self.fail("Failed to raise TypeError") + + def test_functionality_1(self): + answer = self.fold_func(minus, 0, [1, 2, 3]) + self.assertEqual(answer, self.minus_answer) + + def test_functionality_2(self): + self.assertEqual(self.fold_func(add, 0, []), 0) + + def test_errors_pass_through(self): + try: + self.fold_func(fail, 0, [1, 2, 3]) + except RuntimeError: + pass + else: + self.fail("Failed to raise RuntimeError") + + def test_func_not_called_for_empty_seq(self): + self.fold_func(fail, 0, []) + + def test_seq_not_modified(self): + seq = [1, 2, 3] + self.fold_func(add, 0, seq) + + self.assertEqual(seq, [1, 2, 3]) + +from functional import foldr +class Test_foldr(Test_foldl): + fold_func = foldr + minus_answer = 2 + + def test_main(verbose=None): import sys test_classes = ( TestPartial, TestPartialSubclass, TestPythonPartial, + Test_foldl, + Test_foldr ) test_support.run_unittest(*test_classes) Index: Doc/lib/libfunctional.tex =================================================================== --- Doc/lib/libfunctional.tex (revision 42109) +++ Doc/lib/libfunctional.tex (working copy) @@ -5,7 +5,9 @@ \moduleauthor{Peter Harris}{scav@blueyonder.co.uk} \moduleauthor{Raymond Hettinger}{python@rcn.com} +\moduleauthor{Collin Winter}{collinw@gmail.com} \sectionauthor{Peter Harris}{scav@blueyonder.co.uk} +\sectionauthor{Collin Winter}{collinw@gmail.com} \modulesynopsis{Higher-order functions and operations on callable objects.} @@ -15,9 +17,98 @@ that act on or return other functions. In general, any callable object can be treated as a function for the purposes of this module. +To learn more about functional programming, please consult a dedicated +functional programming tutorial. A quick Google search for "python +functional programming" turns up many excellent resources. -The \module{functional} module defines the following function: +In addition to the contents of this module, Python includes a number of other +tools useful for functional programming, such as the built-in functions +\function{sum}, \function{max}, \function{min}, and \function{filter}. + +The \module{functional} module defines the following functions: + +\begin{funcdesc}{foldl}{func,start,iterable} + +\function{foldl} takes a binary function, a starting value (usually some kind +of 'zero'), and an iterable. The function is applied to the starting value and +the first element of the list, then the result of that and the second element +of the list, then the result of that and the third element of the list, and so +on. + +This means that a call such as + + \begin{verbatim} + foldl(f, 0, [1, 2, 3]) + \end{verbatim} + +is equivalent to + + \begin{verbatim} + f(f(f(0, 1), 2), 3) + \end{verbatim} + +\function{foldl} is roughly equivalent to the following recursive function: + + \begin{verbatim} + def foldl(func, start, seq): + if len(seq) == 0: + return start + + return foldl(func, func(start, seq[0]), seq[1:]) + \end{verbatim} + +Speaking of equivalence, the above \function{foldl} call can be expressed in +terms of the built-in \function{reduce} like so: + + \begin{verbatim} + reduce(f, [1, 2, 3], 0) + \end{verbatim} +\end{funcdesc} + +\begin{funcdesc}{foldr}{func,start,iterable} + +\function{foldr} is much like \function{foldl}, except it starts with the last +element, rather than the first element like \function{foldl} does. This +means that a call such as + + \begin{verbatim} + foldr(f, 0, [1, 2, 3]) + \end{verbatim} + +is equivalent to + + \begin{verbatim} + f(1, f(2, f(3, 0))) + \end{verbatim} + +The difference can be seen in the following example: + + \begin{verbatim} + >>> def minus(a, b): + ... return a - b + ... + >>> foldl(minus, 0, [1, 2, 3]) + -6 + >>> foldr(minus, 0, [1, 2, 3]) + 2 + >>> + \end{verbatim} + +\function{foldr} is roughly equivalent to the following recursive function: + + \begin{verbatim} + def foldr(func, start, seq): + if len(seq) == 0: + return start + + return func(seq[0], foldr(func, start, seq[1:])) + \end{verbatim} + +Note that unlike \function{foldl}, \function{foldr} cannot be expressed in +terms of the built-in \function{reduce} function. +\end{funcdesc} + \begin{funcdesc}{partial}{func\optional{,*args}\optional{, **keywords}} Return a new \class{partial} object which when called will behave like \var{func} called with the positional arguments \var{args} and keyword @@ -51,7 +142,6 @@ \end{funcdesc} - \subsection{\class{partial} Objects \label{partial-objects}}