{ "info": { "author": "John Freeman", "author_email": "jfreeman08@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7" ], "description": ".. start-include\n\n===========\ntypeclasses\n===========\n\nExtensible methods for Python mimicking typeclasses in Haskell.\n\n.. image:: https://travis-ci.org/thejohnfreeman/python-typeclasses.svg?branch=master\n :target: https://travis-ci.org/thejohnfreeman/python-typeclasses\n :alt: Build status\n\n.. image:: https://readthedocs.org/projects/python-typeclasses/badge/?version=latest\n :target: https://python-typeclasses.readthedocs.io/\n :alt: Documentation status\n\n.. image:: https://img.shields.io/pypi/v/typeclasses.svg\n :target: https://pypi.org/project/typeclasses/\n :alt: Latest PyPI version\n\n.. image:: https://img.shields.io/pypi/pyversions/typeclasses.svg\n :target: https://pypi.org/project/typeclasses/\n :alt: Python versions supported\n\n\nMotivation\n==========\n\nSome statically typed languages have `ad hoc polymorphism`_ where a function\ncan have multiple implementations depending on the types of its arguments. In\nlanguages like C++ and Java, it is called function overloading. In Haskell, it\nis accomplished with type classes.\n\n.. _`ad hoc polymorphism`: https://en.wikipedia.org/wiki/Ad_hoc_polymorphism\n\nConsider an example of writing a ``toJson`` function in C++. The function\ntakes a single value and returns a string, but it must be implemented\ndifferently for each different type of value:\n\n.. code-block:: cpp\n\n std::string toJson(int i);\n std::string toJson(double d);\n std::string toJson(std::string s);\n\nSome implementations may be \"recursive\", and call the implementation for\nanother type:\n\n.. code-block:: cpp\n\n template \n std::string toJson(std::vector const& xs) {\n ...\n for (T const& x : xs) {\n ... toJson(x) ...\n }\n ...\n }\n\nMany dynamically typed languages, like Python and JavaScript, lack ad hoc\npolymorphism in the language, but developers can implement it by hand by\ninspecting the argument types and dispatching to implementations accordingly:\n\n.. code-block:: python\n\n def to_json(value):\n if isinstance(value, int):\n return ...\n if isinstance(value, float):\n return ...\n if isinstance(value, str):\n return ...\n if isinstance(value, list):\n return '[' + ','.join(to_json(x) for x in value) + ']'\n\nIn addition to being a little uglier, this technique suffers from\na limitation: once we've defined the function, we can't add any more\noverloads. Imagine we want to define a JSON serialization for our\nuser-defined type:\n\n.. code-block:: python\n\n from ... import to_json\n\n @dataclass\n class Person:\n name: str\n\n def to_json_person(person):\n return f'{{\"name\":{to_json(person.name)}}}'\n\n\nWhile this example works for serializing ``Person``, we won't be able to\nserialize a ``list`` of ``Person`` because the implementation of ``to_json``\nfor ``list`` won't call ``to_json_person``.\n\n\nType Classes\n============\n\nIn many languages, e.g. C++ and Java, two functions with the same name but\ndifferent types are called **overloads** of the name.\nIn Haskell, these overloads are not permitted: no two functions (or any other\nvalues for that matter) can have the same name in the same scope.\nHowever, type classes offer a way around this limitation.\n\nA **type class** in Haskell is a group of polymorphic functions, called\n**methods**, parameterized by a single **type variable**.\nThe type class only needs to *declare* the method signatures;\nit does not need to provide any definitions.\n\nAn **instance** for a type class *defines* all the methods of the type class\nfor a specific **type argument** in the place of the type variable.\nIn other words, a type class has exactly one *polymorphic* declaration, but\nmany *monomorphic* instances, one for every possible type argument.\nThus, a method can have many definitions (i.e. implementations), one from each\ninstance, which means it can be overloaded.\n\nAt a method call site, how does Haskell know which overload, from which\ninstance, to use?\nHaskell requires that the signature of the method in the type class\ndeclaration mentions the type variable in one of its parameters or its return\ntype.\nIt tries to unify that polymorphic declaration signature with the call site to\nfill in the type variable; if it succeeds, then it selects the monomorphic\ninstance for that type argument.\n\n\nTutorial\n=========\n\nHow can we replicate type classes in Python?\n\nDecorate a method signature with a call to ``typeclass``, giving it the\nname of a type variable. The decorator will check the signature to make sure\nthat the type variable appears at least once in the type annotations of the\nparameters. Unlike Haskell, Python cannot infer the *return type* at a call\nsite, so that path to instance discovery is impossible; the type variable\n*must* be used as the type of at least one *parameter*.\n\n.. code-block:: python\n\n T = typing.TypeVar('T')\n @typeclass(T)\n def to_json(value: T) -> str:\n \"\"\"Serialize a value to JSON.\"\"\"\n\nWe may optionally provide a default implementation. If we do not, the\ndefault behavior is to raise a ``NotImplementedError`` diagnosing\na missing instance for the specific type variable.\n\nThe ``typeclass`` decorator will add an ``instance`` attribute to the method.\nUse that to decorate monomorphic implementations, giving it the type argument:\n\n.. code-block:: python\n\n @to_json.instance(str)\n def _to_json_str(s):\n return f'\"{s}\"'\n\nWe can decorate an implementation multiple times if it can serve multiple\ninstances:\n\n.. code-block:: python\n\n @to_json.instance(int)\n @to_json.instance(float)\n def _to_json_number(n):\n return str(n)\n\nWe can define an implementation for all types structurally matching\na protocol_. Because it is presently impossible to infer the difference\nbetween a protocol and a type, we must differentiate it for the decorator:\n\n.. _protocol: https://mypy.readthedocs.io/en/latest/protocols.html\n\n.. code-block:: python\n\n @to_json.instance(typing.Iterable, protocol=True)\n def _to_json_iterable(xs):\n return '[' + ','.join(to_json(x) for x in xs) + ']'\n\nIf a type argument matches multiple protocols, the instance that was first\ndefined will be chosen.\n\nNow we can define instances for types whether we defined the type or imported\nit.\n\n.. code-block:: python\n\n @to_json.instance(Person)\n def _to_json_person(person):\n return f'{{\"name\":{to_json(person.name)}}}'\n\n.. code-block:: python\n\n >>> to_json([Person(name='John')])\n [{\"name\":\"John\"}]\n\n\n.. end-include\n", "description_content_type": "text/x-rst", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/thejohnfreeman/python-typeclasses/", "keywords": "", "license": "ISC", "maintainer": "John Freeman", "maintainer_email": "jfreeman08@gmail.com", "name": "typeclasses", "package_url": "https://pypi.org/project/typeclasses/", "platform": "", "project_url": "https://pypi.org/project/typeclasses/", "project_urls": { "Documentation": "https://python-typeclasses.readthedocs.io/", "Homepage": "https://github.com/thejohnfreeman/python-typeclasses/", "Repository": "https://github.com/thejohnfreeman/python-typeclasses/" }, "release_url": "https://pypi.org/project/typeclasses/0.1.0/", "requires_dist": [ "sphinx (>=1.8,<2.0); extra == \"docs\"", "sphinx_rtd_theme (>=0.4.3,<0.5.0); extra == \"docs\"", "toml (>=0.10.0,<0.11.0); extra == \"docs\"" ], "requires_python": ">=3.6-dev,<4.0", "summary": "Extensible methods a la Haskell's typeclasses.", "version": "0.1.0" }, "last_serial": 5474188, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "7c1898fc1781314665f2e3ec21ca510c", "sha256": "6207342498a47810fc520a2509c3a7ae329f61021218af60bdefd04d89d4cd2e" }, "downloads": -1, "filename": "typeclasses-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "7c1898fc1781314665f2e3ec21ca510c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6-dev,<4.0", "size": 8344, "upload_time": "2019-07-01T22:43:17", "url": "https://files.pythonhosted.org/packages/6b/ce/0e8dc83f29db9e78f0a12ea9d00e523a72509acdc1be3b5f71d78a67105e/typeclasses-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "fb96d92f6b0d1f2756717c7101296131", "sha256": "550f3d03bcb13ac64923795e21553303d82917078f3bbdb5a380858bb47e8b7c" }, "downloads": -1, "filename": "typeclasses-0.1.0.tar.gz", "has_sig": false, "md5_digest": "fb96d92f6b0d1f2756717c7101296131", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6-dev,<4.0", "size": 8077, "upload_time": "2019-07-01T22:43:19", "url": "https://files.pythonhosted.org/packages/70/b9/1c8c2aa04fed94d7419a644ecf8437708dc1a5457c4941016f92af6ac745/typeclasses-0.1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "7c1898fc1781314665f2e3ec21ca510c", "sha256": "6207342498a47810fc520a2509c3a7ae329f61021218af60bdefd04d89d4cd2e" }, "downloads": -1, "filename": "typeclasses-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "7c1898fc1781314665f2e3ec21ca510c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6-dev,<4.0", "size": 8344, "upload_time": "2019-07-01T22:43:17", "url": "https://files.pythonhosted.org/packages/6b/ce/0e8dc83f29db9e78f0a12ea9d00e523a72509acdc1be3b5f71d78a67105e/typeclasses-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "fb96d92f6b0d1f2756717c7101296131", "sha256": "550f3d03bcb13ac64923795e21553303d82917078f3bbdb5a380858bb47e8b7c" }, "downloads": -1, "filename": "typeclasses-0.1.0.tar.gz", "has_sig": false, "md5_digest": "fb96d92f6b0d1f2756717c7101296131", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6-dev,<4.0", "size": 8077, "upload_time": "2019-07-01T22:43:19", "url": "https://files.pythonhosted.org/packages/70/b9/1c8c2aa04fed94d7419a644ecf8437708dc1a5457c4941016f92af6ac745/typeclasses-0.1.0.tar.gz" } ] }