{ "info": { "author": "Alex Frieder", "author_email": "alex.frieder@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Other/Nonlisted Topic" ], "description": "########\nMatcher\n########\n\nThis is a port of `Rematch `_, a Typescript pattern matching library.\nMatcher allows for type-safe, functional pattern matching in Python.\n\n===========\nBasic usage\n===========\n\n---------------------\nWithout type-checking\n---------------------\n\n::\n\n from matcher import Matcher\n\n m = Matcher()\n\n def powerLevel(hero):\n return m.match(hero, [\n m.Type(Speedster, lambda hero: print('Speedsters are too fast!'), lambda hero: math.inf),\n m.Values(['Goku', 'Vegeta'], lambda hero: 9001),\n m.Value('Iron Man', lambda hero: 616)\n ])\n\n print(powerLevel('Goku')) # 9001\n print(powerLevel(Speedster.Flash)) # Speedsters are too fast! # inf\n print(powerLevel('Captain America')) # matcher.MatchError: Captain America doesn't match any of the provided clauses\n\n------------------\nWith type-checking\n------------------\n\n::\n\n from matcher import Matcher\n\n m = Matcher[int, str]()\n\n def wrongInput(s: str) -> str:\n return m.match(s, [\n m.Value(1, lambda s: s),\n m.Else(lambda s: s)\n ])\n\n # Argument 1 to \"match\" of \"Matcher\" has incompatible type \"str\"; expected \"int\"\n\n def wrongOutput(n: int) -> Any:\n return m.match(n, [\n m.Values((1, 2, 3), lambda n: n + \"Hello World\"),\n m.Else(lambda n: n**2)\n ])\n\n # Argument 2 to \"Values\" of \"Matcher\" has incompatible type Callable[[int], int]; expected Callable[[int], str]\n\nThe Matcher.match function takes in an argument and a group of cases to test the argument against.\n\nThere are 4 types of cases:\n\n - **Value** - argument matches single value\n - **Values** - argument matches one of multiple values\n - **Type** - argument matches a type\n - **Else** - argument does not match any previous cases\n\nIf no cases are valid, a MatchError is thrown. There are no 'fall-throughs' like in switch statements.\n\n======================================\nWhy use pattern matching over if/else?\n======================================\nFor the large majority of code that isn't performance-sensitive, there are a lot of great reasons why you'd want to use pattern matching over if/else:\n\n - it enforces a common return value and type for each of your branches (when using type definitions)\n - in languages with exhaustiveness checks, it forces you to explicitly consider all cases and noop the ones you don't need\n - it prevents early returns, which become harder to reason about if they cascade, grow in number, or the branches grow longer than the height of your screen (at which point they become invisible). Having an extra level of indentation goes a long way towards warning you you're inside a scope.\n - it can help you identify logic to pull out, rewriting it into a more DRY, debuggable, and testable form.\n\n================\nA longer example\n================\n\nLet's do an example! We're building a webapp, and we need to authenticate our users and update them on their status. Here's a straightforward solution:\n\n::\n\n if isinstance(user, BlacklistedUser):\n warnBlacklistMonitor()\n\n return\n elif user.password == enteredPassword:\n login()\n\n print(\"You're logged in!\")\n else:\n onUserFailedLogin()\n\n print(\"Mistyped your password? Try again or do a password reset.\")\n\nThis code works. Let's see how a pattern matching solution stacks up:\n\n::\n\n from matcher import Matcher\n\n m = Matcher[User, None]()\n m2 = Matcher[str, str]()\n\n m.match(user, [\n m.Type(BlacklistedUser, lambda user: warnBlacklistMonitor()),\n m.Else(lambda user: print(\n m2.match(enteredPassword, [\n m2.Value(user.password, lambda password: login(), lambda password: \"You're logged in!\"),\n m2.Else(lambda password: onUserFailedLogin(), lambda password: f\"Your password isn't {password}!\")\n ])\n ))\n ])\n\nIt's immediately clear that there are 3 return points, and that 2 of them are dependent on the other one. We've factored out the print statement, which'll make debugging / testing easier down the line. And lastly, all the return points consistently return nothing.\n\n==================\nA more fun example\n==================\n\nWe can also calculate Fibonacci numbers using matching!\n\n::\n\n from matcher import Matcher\n\n m = Matcher[int, int]()\n\n cases = [\n m.Values([1, 2], lambda n: 1),\n m.Else(lambda n: m.match(n-1, cases) + m.match(n-2, cases))\n ]\n\n print(m.match(10, cases)) # 55\n\nThis is more in line with the functional definition that fib(1) == fib(2) == 1, and fib(n) == fib(n-1) + fib(n-2).\nDue to the lazy evaluation of the actions provided to the cases, we can use recursion.\n", "description_content_type": null, "docs_url": null, "download_url": "https://github.com/afrieder/matcher/tarball/0.1", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/afrieder/matcher", "keywords": "case", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "matcher", "package_url": "https://pypi.org/project/matcher/", "platform": "", "project_url": "https://pypi.org/project/matcher/", "project_urls": { "Download": "https://github.com/afrieder/matcher/tarball/0.1", "Homepage": "https://github.com/afrieder/matcher" }, "release_url": "https://pypi.org/project/matcher/0.2/", "requires_dist": null, "requires_python": "", "summary": "A simple functional, type-safe pattern matcher in Python", "version": "0.2" }, "last_serial": 2655972, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "57ee034d792f7932f389470cd56edcf8", "sha256": "6c4f51ec8fad627f5073b66bb99516dd82802a75b9d7b0bdc56a82d5b0d7f816" }, "downloads": -1, "filename": "matcher-0.1.tar.gz", "has_sig": false, "md5_digest": "57ee034d792f7932f389470cd56edcf8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 3312, "upload_time": "2017-01-01T00:31:39", "url": "https://files.pythonhosted.org/packages/38/62/19092717797ab8f57189f34e2e8725ea76efd9aa25be3c06468d6638e735/matcher-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "016e4109c94253570ee337c0dbb7f980", "sha256": "fa3214f0fe8b4e67258b33e3df43afa7c0aab77307f9ef914743b1284135fae8" }, "downloads": -1, "filename": "matcher-0.2.tar.gz", "has_sig": false, "md5_digest": "016e4109c94253570ee337c0dbb7f980", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4151, "upload_time": "2017-02-20T20:39:51", "url": "https://files.pythonhosted.org/packages/34/85/faaa5249d905ff9f996a5fc9ac3718a31c5cb21836e54f6fbce778f4ea81/matcher-0.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "016e4109c94253570ee337c0dbb7f980", "sha256": "fa3214f0fe8b4e67258b33e3df43afa7c0aab77307f9ef914743b1284135fae8" }, "downloads": -1, "filename": "matcher-0.2.tar.gz", "has_sig": false, "md5_digest": "016e4109c94253570ee337c0dbb7f980", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4151, "upload_time": "2017-02-20T20:39:51", "url": "https://files.pythonhosted.org/packages/34/85/faaa5249d905ff9f996a5fc9ac3718a31c5cb21836e54f6fbce778f4ea81/matcher-0.2.tar.gz" } ] }