{ "info": { "author": "Adrian W\u0142osiak", "author_email": "adwlosiakh@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "function-pattern-matching\n*************************\n\n**function-pattern-matching** (**fpm** for short) is a module which introduces Erlang-style\n`multiple clause defined functions `_ and\n`guard sequences `_ to Python.\n\nThis module is both Python 2 and 3 compatible.\n\n.. contents:: Table of contents\n\nIntroduction\n============\n\nTwo families of decorators are introduced:\n\n- ``case``: allows multiple function clause definitions and dispatches to correct one. Dispatch happens on the values\n of call arguments or, more generally, when call arguments' values match specified guard definitions.\n\n - ``dispatch``: convenience decorator for dispatching on argument types. Equivalent to using ``case`` and ``guard``\n with type checking.\n\n- ``guard``: allows arguments' values filtering and raises ``GuardError`` when argument value does not pass through\n argument guard.\n\n - ``rguard``: Wrapper for ``guard`` which converts first positional decorator argument to relguard. See Relguards_.\n\n - ``raguard``: Like ``rguard``, but converts return annotation. See Relguards_.\n\nUsage example:\n\n- All Python versions:\n\n.. code-block:: python\n\n import function_pattern_matching as fpm\n\n @fpm.case\n def factorial(n=0):\n return 1\n\n @fpm.case\n @fpm.guard(fpm.is_int & fpm.gt(0))\n def factorial(n):\n return n * factorial(n - 1)\n\n- Python 3 only:\n\n.. code-block:: python\n\n import function_pattern_matching as fpm\n\n @fpm.case\n def factorial(n=0):\n return 1\n\n @fpm.case\n @fpm.guard\n def factorial(n: fpm.is_int & fpm.gt(0)): # Guards specified as annotations\n return n * factorial(n - 1)\n\nOf course that's a poor implementation of factorial, but illustrates the idea in a simple way.\n\n**Note:** This module does not aim to be used on production scale or in a large sensitive application (but I'd be\nhappy if someone decided to use it in his/her project). I think of it more as a fun project which shows how\nflexible Python can be (and as a good training for myself).\n\nI'm aware that it's somewhat against duck typing and EAFP (easier to ask for forgiveness than for permission)\nphilosophy employed by the language, but obviously there *are* some cases when preliminary checks are useful and\nmake code (and life) much simpler.\n\nInstallation\n============\n\nfunction-pattern-matching can be installed with pip::\n\n $ pip install function-pattern-matching\n\nModule will be available as ``function_pattern_matching``. It is recommended to import as ``fpm``.\n\nUsage\n=====\n\nGuards\n------\n\nWith ``guard`` decorator it is possible to filter function arguments upon call. When argument value does not pass\nthrough specified guard, then ``GuardError`` is raised.\n\nWhen global setting ``strict_guard_definitions`` is set ``True`` (the default value), then only ``GuardFunc``\ninstances can be used in guard definitions. If it's set to ``False``, then any callable is allowed, but it is **not**\nrecommended, as guard behaviour may be unexpected (``RuntimeWarning`` is emitted), e.g. combining regular callables\nwill not work.\n\n``GuardFunc`` objects can be negated with ``~`` and combined together with ``&``, ``|`` and ``^`` logical operators.\nNote however, that *xor* isn't very useful here.\n\n**Note:** It is not possible to put guards on varying arguments (\\*args, \\**kwargs).\n\nList of provided guard functions\n................................\n\nEvery following function returns/is a callable which takes only one parameter - the call argument that is to be\nchecked.\n\n- ``_`` - Catch-all. Returns ``True`` for any input. Actually, this can take any number of arguments.\n- ``eq(val)`` - checks if input is equal to *val*\n- ``ne(val)`` - checks if input is not equal to *val*\n- ``lt(val)`` - checks if input is less than *val*\n- ``le(val)`` - checks if input is less or equal to *val*\n- ``gt(val)`` - checks if input is greater than *val*\n- ``ge(val)`` - checks if input is greater or equal to *val*\n- ``Is(val)`` - checks if input is *val* (uses ``is`` operator)\n- ``Isnot(val)`` - checks if input is not *val* (uses ``is not`` operator)\n- ``isoftype(_type)`` - checks if input is instance of *_type* (uses ``isintance`` function)\n- ``isiterable`` - checks if input is iterable\n- ``eTrue`` - checks if input evaluates to ``True`` (converts input to ``bool``)\n- ``eFalse`` - checks if input evaluates to ``False`` (converts input to ``bool``)\n- ``In(val)`` - checks if input is in *val* (uses ``in`` operator)\n- ``notIn(val)`` - checks if input is not in *val* (uses ``not in`` operator)\n\nCustom guards\n.............\n\nAlthough it is not advised (at least for simple checks), you can create your own guards:\n\n- by using ``makeguard`` decorator on your test function.\n\n- by writing a function that returns a ``GuardFunc`` object initialised with a test function.\n\nNote that a test function must have only one positional argument.\n\nExamples:\n\n.. code-block:: python\n\n # use decorator\n @fpm.makeguard\n def is_not_zero_nor_None(inp):\n return inp != 0 and inp is not None\n\n # return GuardFunc object\n def is_not_val_nor_specified_thing(val, thing):\n return GuardFunc(lambda inp: inp != val and inp is not thing)\n\n # equivalent to (fpm.ne(0) & fpm.Isnot(None)) | (fpm.ne(1) & fpm.Isnot(some_object))\n @fpm.guard(is_not_zero_nor_None | is_not_val_nor_specified_thing(1, some_object))\n def guarded(argument):\n pass\n\nThe above two are very similar, but the second one allows creating function which takes multiple arguments to construct\nactual guard.\n\n**Note:** It is not recommended to create your own guard functions. In most cases combinations of the ones shipped with\nfpm should be all you need.\n\nDefine guards for function arguments\n....................................\n\nThere are two ways of defining guards:\n\n- As decorator arguments\n\n - positionally: guards order will match decoratee's (the function that is to be decorated) arguments order.\n\n .. code-block:: python\n\n @fpm.guard(fpm.isoftype(int) & fpm.ge(0), fpm.isiterable)\n def func(number, iterable):\n pass\n\n - as keyword arguments: e.g. guard under name *a* will guard decoratee's argument named *a*.\n\n .. code-block:: python\n\n @fpm.guard(\n number = fpm.isoftype(int) & fpm.ge(0),\n iterable = fpm.isiterable\n )\n def func(number, iterable):\n pass\n\n- As annotations (Python 3 only)\n\n .. code-block:: python\n\n @fpm.guard\n def func(\n number: fpm.isoftype(int) & fpm.ge(0),\n iterable: fpm.isiterable\n ): # this is NOT an emoticon\n pass\n\nIf you try to declare guards using both methods at once, then annotations get ignored and are left untouched.\n\nRelguards\n---------\n\nRelguard is a kind of guard that checks relations between arguments (and/or external variables). ``fpm`` implements\nthem as functions (wrapped in ``RelGuard`` object) whose arguments are a subset of decoratee's arguments (no arguments\nis fine too).\n\nDefine relguard\n...............\n\nThere are a few ways of defining a relguard.\n\n- Using ``guard`` with the first (and only) positional non-keyword argument of type ``RelGuard``:\n\n .. code-block:: python\n\n @fpm.guard(\n fpm.relguard(lambda a, c: a == c), # converts lambda to RelGuard object in-place\n a = fpm.isoftype(int) & fpm.eTrue,\n b = fpm.Isnot(None)\n )\n def func(a, b, c):\n pass\n\n- Using ``guard`` with the return annotation holding a ``RelGuard`` object (Python 3 only):\n\n .. code-block:: python\n\n @fpm.guard\n def func(a, b, c) -> fpm.relguard(lambda a, b, c: a != b and b < c):\n pass\n\n- Using ``rguard`` with a regular callable as the first (and only) positional non-keyword argument.\n\n .. code-block:: python\n\n @fpm.rguard(\n lambda a, c: a == c, # rguard will try converting this to RelGuard object\n a = fpm.isoftype(int) & fpm.eTrue,\n b = fpm.Isnot(None)\n )\n def func(a, b, c):\n pass\n\n- Using ``raguard`` with a regular callable as the return annotation.\n\n .. code-block:: python\n\n @fpm.raguard\n def func(a, b, c) -> lambda a, b, c: a != b and b < c: # raguard will try converting lambda to RelGuard object\n pass\n\nAs you can see, when using ``guard`` you have to manually convert functions to ``RelGuard`` objects with ``relguard``\nmethod. By using ``rguard`` or ``raguard`` decorators you don't need to do it by yourself, and you get a bit cleaner\ndefinition.\n\nMultiple function clauses\n-------------------------\n\nWith ``case`` decorator you are able to define multiple clauses of the same function.\n\nWhen such a function is called with some arguments, then the first matching clause will be executed. Matching clause\nwill be the one that didn't raise a ``GuardError`` when called with given arguments.\n\n**Note:** using ``case`` or ``dispatch`` (discussed later) disables default functionality of default argument values.\nFunctions with varying arguments (\\*args, \\**kwargs) and keyword-only arguments (py3-only) are not supported.\n\nExample:\n\n.. code-block:: python\n\n @fpm.case\n def func(a=0): print(\"zero!\")\n\n @fpm.case\n def func(a=1): print(\"one!\")\n\n @fpm.case\n @fpm.guard(fpm.gt(9000))\n def func(a): print(\"IT'S OVER 9000!!!\")\n\n @fpm.case\n def func(a): print(\"some var:\", a) # catch-all clause\n\n >>> func(0)\n 'zero!'\n >>> func(1)\n 'one!'\n >>> func(9000.1)\n \"IT'S OVER 9000!!!\"\n >>> func(1337)\n 'some var: 1337'\n\nIf no clause matches, then ``MatchError`` is raised. The example shown above has a catch-all clause, so ``MatchError``\nwill never occur.\n\nDifferent arities (argument count) are allowed and are dispatched separetely.\n\nExample:\n\n.. code-block:: python\n\n @fpm.case\n def func(a=1, b=1, c):\n return 1\n\n @fpm.case\n def func(a, b, c):\n return 2\n\n @fpm.case\n def func(a=1, b=1, c, d):\n return 3\n\n @fpm.case\n def func(a, b, c, d):\n return 4\n\n >>> func(1, 1, 'any')\n 1\n >>> func(1, 0, 0.5)\n 2\n >>> func(1, 1, '', '')\n 3\n >>> func(1, 0, 0, '')\n 4\n\nAs you can see, clause order matters only for same-arity clauses. 4-arg catch-all does not affect any 3-arg definition.\n\nDefine multi-claused functions\n..............................\n\nThere are three ways of defining a pattern for a function clause:\n\n- Specify exact values as decorator arguments (positional and/or keyword)\n\n .. code-block:: python\n\n @fpm.case(1, 2, 3)\n def func(a, b, c):\n pass\n\n @fpm.case(1, fpm._, 0)\n def func(a, b, c):\n pass\n\n @fpm.case(b=10)\n def func(a, b, c):\n pass\n\n- Specify exact values as default arguments\n\n .. code-block:: python\n\n @fpm.case\n def func(a=0):\n pass\n\n @fpm.case\n def func(a=10):\n pass\n\n @fpm.case\n def func(a=fpm._, b=3):\n pass\n\n- Specify guards for clause to match\n\n .. code-block:: python\n\n @fpm.case\n @fpm.guard(fpm.eq(0) & ~fpm.isoftype(float))\n def func(a):\n pass\n\n @fpm.case\n @fpm.guard(fpm.gt(0))\n def func(a):\n pass\n\n @fpm.case\n @fpm.guard(fpm.Is(None))\n def func(a):\n pass\n\n``dispatch`` decorator\n......................\n\n``dispatch`` decorator is similar to ``case``, but it lets you to define argument types to match against. You can\nspecify types either as decorator arguments or default values (or as guards, of course, but it makes using ``dispatch``\npointless).\n\nExample:\n\n.. code-block:: python\n\n @fpm.dispatch(int, int)\n def func(a, b):\n print(\"integers\")\n\n @fpm.dispatch\n def func(a=float, b=float):\n print(\"floats\")\n\n >>> func(1, 1)\n 'integers'\n >>> func(1.0, 1.0)\n 'floats'\n\nExamples (the useful ones)\n==========================\n\nStill working on this section!\n\n- Ensure that an argument is a list of strings. Prevent feeding string accidentally, which can cause some headache,\n since both are iterables.\n\n - Option 1: do not allow strings\n\n .. code-block:: python\n\n # thanks to creshal from HN for suggestion\n\n lookup = {\n \"foo\": 1,\n \"bar\": 2,\n \"baz\": 3\n }\n\n @fpm.guard\n def getSetFromDict(\n dict_, # let it throw TypeError if not a dict. Will be more descriptive than a GuardError.\n keys: ~fpm.isoftype(str)\n ):\n \"Returns a subset of elements of dict_\"\n ret_set = set()\n for key in keys:\n try:\n ret_set.add(dict_[key])\n except KeyError:\n pass\n return ret_set\n\n getSetFromDict(lookup, ['foo', 'baz', 'not-in-lookup']) # will return two-element set\n getSetFromDict(lookup, 'foo') # raises GuardError, but would return empty set without guard!\n\nSimilar solutions\n=================\n\n- `singledispatch `_ from functools\n- `pyfpm `_\n- `patmatch `_\n- http://blog.chadselph.com/adding-functional-style-pattern-matching-to-python.html\n- http://www.artima.com/weblogs/viewpost.jsp?thread=101605 (by Guido van Rossum, BDFL)\n\nLicense\n=======\n\nMIT (c) Adrian W\u0142osiak", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/rasguanabana/function-pattern-matching", "keywords": "pattern matching guards", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "function-pattern-matching", "package_url": "https://pypi.org/project/function-pattern-matching/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/function-pattern-matching/", "project_urls": { "Homepage": "https://github.com/rasguanabana/function-pattern-matching" }, "release_url": "https://pypi.org/project/function-pattern-matching/0.99a2/", "requires_dist": [ "six" ], "requires_python": "", "summary": "Pattern matching and guards for Python functions", "version": "0.99a2" }, "last_serial": 2122321, "releases": { "0.99a1": [ { "comment_text": "", "digests": { "md5": "343cb4345fd735c18a446741d92455f6", "sha256": "2dd65194a14d9e219d7824854b9e54a39e4ad252fc72386ff661af33f110d5c9" }, "downloads": -1, "filename": "function_pattern_matching-0.99a1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "343cb4345fd735c18a446741d92455f6", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 15775, "upload_time": "2016-05-11T18:44:53", "url": "https://files.pythonhosted.org/packages/37/d4/f412d8dd3613e21c5776c2f7e55ac68fb3e421b60a88f583bd2de30db90b/function_pattern_matching-0.99a1-py2.py3-none-any.whl" } ], "0.99a2": [ { "comment_text": "", "digests": { "md5": "df42dcca8ae0629f3b66d6f72dec9d14", "sha256": "e722055bb9e19a3350d3dfbfe0a4c8c9f0c1dad4f5c8fa3826cd37050258823e" }, "downloads": -1, "filename": "function_pattern_matching-0.99a2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "df42dcca8ae0629f3b66d6f72dec9d14", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 17259, "upload_time": "2016-05-18T19:20:02", "url": "https://files.pythonhosted.org/packages/a3/9d/11c6e9930de34bce39970cdbfe185d4305e4d0027589d782ac3d12b2b3b8/function_pattern_matching-0.99a2-py2.py3-none-any.whl" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "df42dcca8ae0629f3b66d6f72dec9d14", "sha256": "e722055bb9e19a3350d3dfbfe0a4c8c9f0c1dad4f5c8fa3826cd37050258823e" }, "downloads": -1, "filename": "function_pattern_matching-0.99a2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "df42dcca8ae0629f3b66d6f72dec9d14", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 17259, "upload_time": "2016-05-18T19:20:02", "url": "https://files.pythonhosted.org/packages/a3/9d/11c6e9930de34bce39970cdbfe185d4305e4d0027589d782ac3d12b2b3b8/function_pattern_matching-0.99a2-py2.py3-none-any.whl" } ] }