{ "info": { "author": "Jason DeLaat", "author_email": "jason.develops@gmail.com", "bugtrack_url": null, "classifiers": [ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Utilities" ], "description": "=======\r\nPyMonad\r\n=======\r\n\r\nPyMonad is a small library \r\nimplementing monads and related data abstractions\r\n-- functors, applicative functors, and monoids --\r\nfor use in implementing functional style programs.\r\nFor those familiar with monads in Haskell,\r\nPyMonad aims to implement many of the features you're used to\r\nso you can use monads in python quickly and easily.\r\nFor those who have never used monads but are interested,\r\nPyMonad is an easy way to learn about them in, perhaps, \r\na slightly more forgiving environment\r\nwithout needing to learn Haskell.\r\n\r\nFeatures\r\n========\r\n\r\n* Easily define curried functions with the ``@curry`` decorator.\r\n* Straight-forward partial application: just pass a curried function the number of arguments you want.\r\n* Composition of curried functions using ``*``.\r\n* Functor, Applicative Functor, and Monad operators: ``*``, ``&``, and ``>>``\r\n* Monoids - int, float, str, list, List, Maybe, First, and Last\r\n* Six predefined monad types\r\n\r\n1. Maybe - for when a calculation might fail\r\n2. Either - Similar to Maybe but with additional error reporting\r\n3. List - For non-deterministic calculations\r\n4. Reader - For sequencing calculations which all access the same data.\r\n5. Writer - For keeping logs of program execution.\r\n6. State - Simulating mutable state in a purely functional way.\r\n\r\nGetting Started\r\n===============\r\n\r\nInstallation\r\n------------\r\n\r\nUsing pip::\r\n\r\n pip install PyMonad\r\n\r\nOr download the package or clone the `git repository from bitbucket `_ and run::\r\n\r\n python setup.py install\r\n\r\nfrom the project directory.\r\n \r\nImports\r\n-------\r\n\r\nImport the entire package::\r\n \r\n from pymonad import *\r\n\r\nOr just a single monad type::\r\n\r\n from pymonad.Maybe import *\r\n\r\nIf you're not importing everything\r\nbut want to use curried functions::\r\n\r\n from pymonad.Reader import curry\r\n\r\nCurried Functions and Partial Application\r\n-----------------------------------------\r\n\r\nTo define a curried function\r\nuse the ``@curry`` decorator::\r\n\r\n @curry\r\n def add(x, y):\r\n return x + y\r\n\r\n @curry\r\n def func(x, y, z):\r\n # Do something with x, y and z.\r\n ...\r\n\r\nThe above fuctions can be partially applied \r\nby passing them less than their full set of arguments::\r\n\r\n add(7, 8) # Calling 'add' normally returns 15 as expected.\r\n add7 = add(7) # Partial application: 'add7' is a function taking one argument.\r\n add7(8) # Applying the final argument retruns 15...\r\n add7(400) # ... or 407, or whatever.\r\n\r\n # 'func' can be applied in any of the following ways.\r\n func(1, 2, 3) # Call func normally.\r\n func(1, 2)(3) # Partially applying two, then applying the last argument.\r\n func(1)(2, 3) # Partially applying one, then applying the last two arguments.\r\n func(1)(2)(3) # Partially applying one, partially applying again, then applying the last argument.\r\n\r\nFunction Composition\r\n--------------------\r\n\r\nCurried functions can be composed with the ``*`` operator.\r\nFunctions are applied from right to left::\r\n \r\n # Returns the first element of a list.\r\n @curry\r\n def head(aList): \r\n return aList[0]\r\n\r\n # Returns everything except the first element of the list.\r\n @curry \r\n def tail(aList): \r\n return aList[1:]\r\n\r\n second = head * tail # 'tail' will be applied first, then its result passed to 'head'\r\n second([1, 2, 3, 4]) # returns 2\r\n\r\nYou can also compose partially applied functions::\r\n\r\n @curry\r\n def add(x, y): \r\n return x + y\r\n\r\n @curry\r\n def mul(x, y): \r\n return x * y\r\n\r\n comp = add(7) * mul(2) # 'mul(2)' is evaluated first, and it's result passed to 'add(7)'\r\n comp(4) # returns 15\r\n\r\n # Composition order matters!\r\n comp = mul(2) * add(7)\r\n comp(4) # returns 22\r\n\r\nFunctors, Applicative Functors, and Monads\r\n------------------------------------------\r\n\r\nAll Monads are also Applicative Functors,\r\nand all Applicative Functors are also Functors,\r\nthough the same is not necessarily true in reverse.\r\nAll the types included with PyMonad\r\nare defined as all three\r\nbut you can define new types however you want.\r\n\r\nAll of these types ultimately derive from ``Container``\r\nwhich simply holds a value and provides some basic value equality checking.\r\nThe method ``getValue()`` will allow you to \"extract\" the value \r\nof monadic computations if/when necessary.\r\n\r\nFunctors\r\n--------\r\n\r\nAll functors define the ``fmap`` method\r\nwhich can be invoked via the fmap operator ``*``.\r\n``fmap`` takes functions which operate on simple types\r\n-- integers, strings, etc. --\r\nand allows them to operate of functor types::\r\n \r\n from pymonad.Maybe import *\r\n from pymonad.List import *\r\n\r\n # 'neg' knows nothing about functor types...\r\n def neg(x):\r\n return -x\r\n\r\n # ... but that doesn't stop us from using it anyway.\r\n neg * Just(9) # returns Just(-9)\r\n neg * Nothing # returns Nothing\r\n neg * List(1, 2, 3, 4) # returns List(-1, -2, -3, -4)\r\n\r\nNotice that the function is on the left\r\nand the functor type is on the right.\r\nIf you think of ``*`` as a sort of fancy opening paren,\r\nthen normal calls and ``fmap`` calls have basically the same structure::\r\n\r\n ------------------------------------------------------------------\r\n function open argument close\r\n Normal call neg ( 9 )\r\n fmap call neg * Just(9)\r\n ------------------------------------------------------------------\r\n\r\n\r\nNotice that ``*`` is also the function composition operator.\r\nIn fact,\r\ncurried functions are instances of the ``Reader`` monad,\r\nand ``fmap`` -ing a function over another function\r\nis the same thing as function composition.\r\n\r\nApplicative Functors\r\n--------------------\r\n\r\nFunctors allow you to use normal functions of a single argument\r\n-- like ``neg`` above --\r\nwith functor types.\r\nApplicative Functors extend that capability\r\n-- via ``amap`` and its operator ``&`` --\r\nallowing you to use normal functions of multiple arguments\r\nwith functor types::\r\n\r\n # 'add' operates on simple types, not functors or applicatives...\r\n def add(x, y):\r\n return x + y\r\n\r\n # ... but we're going to use it on those types anyway.\r\n # Note that we're still using '*' but now in conjunction with '&'\r\n add * Just(7) & Just(8) # returns Just(15)\r\n add * Nothing & Just(8) # returns Nothing\r\n add * Just(7) & Nothing # returns Nothing\r\n add * List(1, 2, 3) & List(4, 5, 6) # returns List(5, 6, 7, 6, 7, 8, 7, 8, 9)\r\n\r\nIf ``*`` is a fancy paren,\r\n``&`` is the fancy comma\r\nused to separate arguments.\r\n\r\nMonads\r\n------\r\n\r\nMonads allow you to sequence a series of calculations\r\nwithin than monad\r\nusing the ``bind`` operator ``>>``.\r\n\r\nThe first argument to ``>>`` is a monad type.\r\nThe second argument is a function\r\nwhich takes a single,\r\nnon-monad argument\r\nand returns an instance of the same monad::\r\n\r\n from pymonad.List import *\r\n from pymonad.Reader import curry\r\n\r\n # Takes a simple number type and returns a 'List' containing that value and it's negative.\r\n def positive_and_negative(x):\r\n return List(x, -x)\r\n\r\n # You can call 'positive_and_negative' normally.\r\n positive_and_negative(9) # returns List(9, -9)\r\n\r\n # Or you can create a List...\r\n x = List(9)\r\n\r\n # ... and then use '>>' to apply positive_and_negative'\r\n x >> positive_and_negative # also returns List(9, -9)\r\n\r\n # But 'x' could also have more than one value...\r\n x = List(1, 2)\r\n x >> positive_and_negative # returns List(1, -1, 2, -2)\r\n\r\n # And of course you can sequence partially applied functions.\r\n @curry\r\n def add_and_sub(x, y):\r\n return List(y + x, y - x)\r\n\r\n List(2) >> positive_and_negative >> add_and_sub(3) # creates List(2)\r\n # applies positive_and_negative: List(2, -2)\r\n # then add_and_sub(3): List(5, -1, 1, -5)\r\n # final result: List(5, -1, 1, -5)\r\n\r\nVariable assignment in monadic code\r\n-----------------------------------\r\n\r\nThe second argument to ``>>`` is a function \r\nwhich takes a single, non-monad argument.\r\nBecause of that, \r\nyou can use ``lambda`` to assign values to a variable\r\nwithing monadic code,\r\nlike this::\r\n \r\n from pymonad.Maybe import *\r\n\r\n Just(9) >> (lambda x: # Here, 'x' takes the value '9'\r\n Just(8) >> (lambda y: # And 'y' takes the value '8'\r\n Just(x + y))) # The final returned value is 'Just(9 + 8)', or 'Just(17)'\r\n\r\nYou can also simply ignore values if you wish::\r\n\r\n Just(9) >> Just(8) # The '9' is thrown away and the result of this computation is 'Just(8)'\r\n\r\nImplementing Monads\r\n-------------------\r\n\r\nImplementing other functors, applicatives, or monads is fairly straight-forward.\r\nThere are three classes, \r\nserving as interfaces::\r\n\r\n Monad --> Applicative --> Functor\r\n\r\nTo implement a new functor,\r\ncreate a new class which derives from ``Functor``\r\nand override the ``fmap`` method.\r\n\r\nTo implement a new applicative functor,\r\ncreate a new class which derives from ``Applicative``\r\nand override the ``amap`` and ``fmap`` methods.\r\n\r\nTo implement a new monad,\r\ncreate a new class which derives from ``Monad``\r\nand override at least the ``bind`` method, \r\nand preferably the ``amap`` and ``fmap`` methods as well.\r\n\r\nThe operators, ``*``, ``&``, and ``>>``\r\nare pre-defined to call the above methods\r\nso you shouldn't need to touch them directly.\r\n\r\nunit (aka return)\r\n-----------------\r\n\r\nThe previous version of pymonad\r\ndidn't include the method ``unit``\r\n(called ``return`` in Haskell).\r\n``unit`` takes a bare value,\r\nsuch as ``8``,\r\nand places it in a default context for that monad.\r\nHaskell allows polymorphism on return types \r\nas well as supporting type inference,\r\nso you (mostly) don't have to tell ``return`` what types to expect,\r\nit just figures it out.\r\nWe can't do that in Python,\r\nso you *always* need to tell ``unit`` what type you're expecting.\r\n\r\nThe ``unit`` method is implemented as a class method\r\nin Functor.py, so it can be used with any functor, applicative or monad.\r\nThere is also a ``unit`` *function* which expects a functor type\r\n(though you can also give it an instance) \r\nand a value\r\nand invokes the corresponding ``unit`` method.\r\nIt is provided to give a more \"functional look\" to code,\r\nbut use whichever method you prefer.\r\nWith the Maybe monad for example:\r\n\r\n1. Maybe.unit(8) # returns Just(8)\r\n2. unit(Maybe, 8) # also returns Just(8)\r\n\r\nIn either case all functors (and applicatives and monads) should implement the ``unit`` class method.\r\n\r\nMonoids\r\n=======\r\n\r\nMonoids are a data type \r\nwhich consists of some operation for combining values of that type,\r\nand an identity value for that operation.\r\nThe operation is called ``mplus`` \r\nand the identity value is callled ``mzero``.\r\nDespite the names,\r\nthey are not necessarily addition and zero.\r\nThey *can* be addition and zero though,\r\nnumbers are sort of the typical monoid.\r\n\r\nIn the case of numbers,\r\nzero is the identity element and addition is the operation.\r\nMonoids adhere to the following laws:\r\n\r\n1. Left and right identity: x + 0 = 0 + x = x \r\n2. Associativity: (x + y) + z = x + (y + z) = x + y + z \r\n\r\nStings are also monoids with the identity element ``mzero`` equal to the empty string,\r\nand the operation ``mplus`` concatenation.\r\n\r\nCreating New Monoids\r\n--------------------\r\n\r\nTo create a new monoids type\r\ncreate a class deriving from ``Monoid``\r\nand override the ``mzero`` static method\r\nwhich takes no arguments and should return an instance of the class\r\ncontaining the identity value for the monoid.\r\nAlso override the ``mplus`` method.\r\nFor instance,\r\nnumbers can be a monoid in two ways,\r\none way with zero and addition as discussed above\r\nand the other way with one and multiplication.\r\nWe could implement that like this::\r\n\r\n class ProductMonoid(Monoid):\r\n @staticmethod\r\n def mzero():\r\n return ProductMonoid(1)\r\n\r\n def mplus(self, other):\r\n return ProductMonoid(self.getValue() * other.getValue())\r\n\r\nThe ``+`` operator (aka __add__()) is defined to call ``mplus`` on monoid instances,\r\nso you can simply \"add\" monoid values together rather than having to call ``mplus`` directly.\r\n\r\n\"Natural\" Monoids\r\n-----------------\r\n\r\nSimilar to ``unit`` for monads,\r\nthere is an ``mzero`` function\r\nwhich expects a type and can be used instead of the ``mzero`` method.\r\nUnlike ``unit`` however,\r\nthe ``mzero`` function serves another purpose.\r\nNumbers, strings and lists can all be used as monoids\r\nand all already have an appropriate definition for ``+``.\r\nWhat they don't have is an ``mzero`` method.\r\nTo allow numbers, strings and lists to be used as monoids\r\nwithout any extra work,\r\nthe ``mzero`` *function* will return the appropriate value for these types\r\nand will attempt to call the ``mzero`` method on anything else.\r\nFor instance::\r\n\r\n mzero(int) # returns 0, also works with float\r\n mzero(str) # returns \"\"\r\n mzero(list) # returns []\r\n mzero(ProductMonoid) # return ProductMonoid(1)\r\n # etc...\r\n\r\nIf you write code involving monoids,\r\nand you're not sure what type of monoid you might be handed,\r\nyou should use the ``mzero`` *function* \r\nand *not* the ``mzero`` method.\r\n\r\nMonoids and the Writer Monad\r\n============================\r\n\r\nThe Writer monad performs calculations \r\nand keeps a log.\r\nThe log can be any monoid type\r\n-- strings being a typical example.\r\n\r\nThe ``Writer`` class doesn't have a default log type,\r\nso to use Writer you need to inherit from it.\r\nIt is extremely simple as the only thing you need to do is define the log type.\r\nFor instance::\r\n\r\n class StringWriter(Writer):\r\n logType = str\r\n\r\nThat's it.\r\nEverything else is already defined by ``Writer``.\r\n``StringWriter``, ``NumberWriter``, and ``ListWriter``\r\nare already defined in the ``Writer`` module for you to use.\r\n\r\nCalling ``unit`` with a ``Writer`` class\r\npackages whatever value you give it\r\nwith the ``mzero`` of the log type::\r\n\r\n unit(StringWriter, 8) # Returns Writer(8, \"\")\r\n\r\n``Writer`` constructors take two values,\r\nthe first being the result of whatever calculation you've just performed,\r\nthe second being the log message \r\n-- or value, or whatever --\r\nto add to the log.\r\n\r\n### Other Methods ###\r\n\r\n``getValue()``: Returns the result and log as a two-tuple.\r\n\r\n``getResult()``: Returns only the result.\r\n\r\n``getLog()``: Returns only the log.\r\n\r\nA quick example::\r\n\r\n @curry\r\n def add(x, y):\r\n return StringWriter(x + y, \"Adding \" + str(x) + \" and \" + str(y) + \". \")\r\n\r\n x = unit(StringWriter, 8) >> add(4) >> add(5)\r\n print(x.getResult()) # prints 17\r\n print(x.getLog()) # prints \"Adding 8 and 4. Adding 12 and 5. \"\r\n\r\nIn the definition of ``add`, \r\n``StringWriter`` could have also been just ``Writer``.\r\nIt's really only necessary to use subclasses when using ``unit``,\r\nbecause ``unit`` checks for the ``logType`` variable.\r\nOtherwise simply giving plain old ``Writer`` a string\r\n-- or other monoid type argument --\r\naccomplishes the same thing.\r\nBoth ``unit`` and ``bind`` (or ``>>``)\r\nconvert ``*Writer`` types to plain ``Writer``\r\nbut using ``StringWriter``\r\n-- or whatever --\r\nmakes your intentions more clear.\r\n\r\nState Monad\r\n===========\r\n\r\nUnlike most of the other monad types,\r\nthe state monad doesn't wrap values\r\nit wraps functions.\r\nSpecifically,\r\nit wraps functions which accept a single 'state' argument\r\nand produce a result and a new 'state' as a 2-tuple.\r\nThe 'state' can be anything:\r\nsimple types like integers,\r\nlists, dictionaries, custom objects/data types,\r\nwhatever.\r\nThe important thing \r\nis that any given chain of stateful computations\r\nall use the same type of state.\r\n\r\nThe ``State`` constuctor should only be used to create stateful computations.\r\nTrying to use ``State`` to inject values,\r\nor even non-stateful functions,\r\ninto the monad will cause it to function incorrectly.\r\nTo inject values,\r\nuse the ``unit`` function.\r\n\r\nHere's an example of using ``State``.\r\nWe'll create a little system which can perform addition and subtraction.\r\nOur total will never be allowed to drop below zero.\r\nThe state that we'll be keeping track of is a simple count \r\nof the total number of operations performed.\r\nEvery time we perform an addition or subtraction\r\nthe count will go up by one::\r\n\r\n\t@curry\r\n\tdef add(x, y):\r\n\t\treturn State(lambda old_state: (x + y, old_state + 1))\r\n\r\n\t@curry\r\n\tdef subtract(y, x):\r\n\t\t@State\r\n\t\tdef state_computation(old_state):\r\n\t\t\tif x - y < 0: \r\n\t\t\t\treturn (0, old_state + 1)\r\n\t\t\telse:\r\n\t\t\t\treturn (x - y, old_state + 1)\r\n\t\treturn state_computation\r\n\r\nAs mentioned,\r\nThe ``State`` constructor takes a function which accepts a 'state',\r\nin this case simply an integer, \r\nand produces a result and a new state as a tuple.\r\nAlthough we could have done ``subtract`` as a one-liner,\r\nI wanted to show that,\r\nif your computation is more complex than can easily be contained in a ``lambda`` expression,\r\nyou can use ``State`` as a decorator to define the stateful computation.\r\n\r\nUsing these functions is now simple::\r\n\r\n\tx = unit(State, 1) >> add(2) >> add(3) >> subtract(40) >> add(5)\r\n\r\n``x`` now contains a stateful computation but that computation hasn't been executed yet.\r\nSince ``State`` values contain functions,\r\nyou can call them like functions \r\nby supplying an initial state value::\r\n\t\r\n\ty = x(0) # Since we're counting the total number of operations, we start at zero.\r\n\tprint(y) # Prints (5, 4), '5' is the result and '4' is the total number of operations performed.\r\n\r\nCalling a ``State`` function in this way will always return the (result, state) tuple.\r\nIf you're only interested in the result::\r\n\r\n\ty = x.getResult(0) # Here 'y' takes the value 5, the result of the computataion.\r\n\r\nOr if you only care about the final state::\r\n\r\n\ty = x.getState(0) # Here 'y' takes the value 4, the final state of the computation.\r\n\r\nChanges\r\n=======\r\n\r\nv1.3, 2014-06-28 -- Added Monoid instances for List and Maybe, added First and Last Monoids, add State Monad, updated tests for List and Maybe.\r\n\r\nV1.2, 2014-05-17 -- Added Monoids and Writer Monad. Added 'unit' class method to existing monad types. Added 'unit' function. Updated README.txt for new features.\r\n\r\nV1.1, 2014-04-13 -- Fixed problems with List, Either and Maybe to make package compatible with Python 2\r\n\r\nV1.0, 2014-03-29 -- Initial Release", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://bitbucket.org/jason_delaat/pymonad", "keywords": "", "license": "Copyright (c) 2014, Jason DeLaat\r\nAll rights reserved.\r\n\r\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\r\n\r\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\r\n\r\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\r\n\r\n3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\r\n\r\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.", "maintainer": "", "maintainer_email": "", "name": "PyMonad", "package_url": "https://pypi.org/project/PyMonad/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/PyMonad/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://bitbucket.org/jason_delaat/pymonad" }, "release_url": "https://pypi.org/project/PyMonad/1.3/", "requires_dist": null, "requires_python": null, "summary": "Collection of classes for programming with functors, applicative functors and monads.", "version": "1.3" }, "last_serial": 1140658, "releases": { "1.0": [ { "comment_text": "", "digests": { "md5": "884f6f055b76d664c83fb544885424e4", "sha256": "3a20a8a94514a0c1dcbcff92a07771324e55a0d9e9084c2beb9d42cf641e8a2d" }, "downloads": -1, "filename": "PyMonad-1.0.tar.gz", "has_sig": false, "md5_digest": "884f6f055b76d664c83fb544885424e4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13835, "upload_time": "2014-03-29T11:59:30", "url": "https://files.pythonhosted.org/packages/6d/93/9f6d59cc25a5a562bbcb82d5afe31923f99ef447c0778a830dc9132bba76/PyMonad-1.0.tar.gz" } ], "1.1": [ { "comment_text": "", "digests": { "md5": "a1e5220f3e66fb92e43514c079a77ccb", "sha256": "e1eaf04debbf729aa9c521d93d4bfd8bf1ef43ef61b463402ce70cb7df37d17d" }, "downloads": -1, "filename": "PyMonad-1.1.tar.gz", "has_sig": false, "md5_digest": "a1e5220f3e66fb92e43514c079a77ccb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14188, "upload_time": "2014-04-13T12:38:59", "url": "https://files.pythonhosted.org/packages/50/92/c993c522bd5fb91e7224c945a86ab1f7ef619221aa4d2b41b6f684699a77/PyMonad-1.1.tar.gz" } ], "1.2": [ { "comment_text": "", "digests": { "md5": "085ecd7ec51b69b7b1ef365557de612d", "sha256": "b03c215e8eb21289d3995525c8bd5df981323092eebaaa868964e27261395351" }, "downloads": -1, "filename": "PyMonad-1.2.tar.gz", "has_sig": false, "md5_digest": "085ecd7ec51b69b7b1ef365557de612d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21988, "upload_time": "2014-05-17T11:51:49", "url": "https://files.pythonhosted.org/packages/af/dd/3d1814942880f80b934e606a4b8b6f4185fdab13d45615486ac367e95a6b/PyMonad-1.2.tar.gz" } ], "1.3": [ { "comment_text": "", "digests": { "md5": "edf3e8b4d54760c41c9a6ab22759e625", "sha256": "a3a2621bb2175d4e1c710c52338991a63f22160640b1f34571a6f90f9a689764" }, "downloads": -1, "filename": "PyMonad-1.3.tar.gz", "has_sig": false, "md5_digest": "edf3e8b4d54760c41c9a6ab22759e625", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25315, "upload_time": "2014-06-28T11:57:36", "url": "https://files.pythonhosted.org/packages/6b/c5/f1affc732c35903266b164c26dea2fda56c8a2eb498d18bcd38349c66f5b/PyMonad-1.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "edf3e8b4d54760c41c9a6ab22759e625", "sha256": "a3a2621bb2175d4e1c710c52338991a63f22160640b1f34571a6f90f9a689764" }, "downloads": -1, "filename": "PyMonad-1.3.tar.gz", "has_sig": false, "md5_digest": "edf3e8b4d54760c41c9a6ab22759e625", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25315, "upload_time": "2014-06-28T11:57:36", "url": "https://files.pythonhosted.org/packages/6b/c5/f1affc732c35903266b164c26dea2fda56c8a2eb498d18bcd38349c66f5b/PyMonad-1.3.tar.gz" } ] }