Package: hoopy
hoopyHoopy extends the Python language, letting you write a subset of Haskell in your scripts!pip install hoopy(not yet published)Example code# coding: hoopydef($)(f,x):returnfxfrommoduleimport(??)print$None??42# prints 42add=(+)# note the parentheses, $ has looser precedence than ==assert2+2==2`add`2==add22==(add$2$2)SetupThe Hoopy library hooks into the Python import system using a custom codec. In a file separate from the
rest of your code, include the following:importhoopyhoopy.register()importyour_entry_point_moduleThis ensures that Hoopy is properly initialized before your code is able to be parsed.Your python files must use a special# coding: hoopydeclaration at the start of the file to be processed by Hoopy. Otherwise, it will be parsed normally by the Python interpreter and probably immediately raise a dozen syntax errors.Custom operatorsHoopy aims to provide a pythonic API to custom operators. This means that they can be defined
both as operator overloads on an object, as well as global functions inside a module:# coding: hoopydef(^-^)(x,y):returnx**2-y**2classBox:def__init__(self,value):self.value=valuedef(^-^)(self,other):returnself.value^-^otherprint(Box(3)^-^2)# prints 5Note that all operators defined in this way are left-associative, and their precedence is determined
statically based on the first character of the operator, following the table below. They may differ slightly from what you would expect from Haskell, which is unavoidable as Haskell operators use all sorts of custom precedence levels.First characterPrecedence levelCorresponding Python operators$0or?1and=,~2==,!=,>,<,>=,<=,in,is,not in,is not(The python operators have special "comparison" associativity)|3|^4^&5&<,>6<<,>>+,-,:7+,-*,/,%,@,.,!, as well as partial application and infixified functions8*,/,//,%,@(none)9**(Right-associative)There are a number of custom operators that will introduce ambiguity in the grammar if defined, such as:,+=, etc. This includes most operators already used by python, as well as those that may be confused with a binary operator followed by a unary operator. To fix this issue, use a backtick character (`) in your custom operators to mark it as a "verbatim" operator. This means that instead ofx >>= y, you should writex >>=` y.Custom operators may also be imported from other modules:fromdata.functorimport(<$>)Extensions to function syntaxHoopy allows you to do a number of extra things with functions:Turn operators into objects:(+)which is treated as a callable object semantically equivalent to(lambda x, y: x + y)Infixify named functions:x `div` yis semantically equivalent todiv(x, y)Perform partial application on function objects:f xpartially appliesxto the argument list off. Ifftakes N positional arguments, thenf a1 a2 ... anis equivalent tof(a1, a2, ..., an). Iffis variadic, or has optional arguments, then the function will be called once the minimum number of necessary arguments have been partially applied. This means that e.g.print a braises an exception, since it's interpreted asprint(a)(b), causing a type error on the second function call. A partially applied function can also be called normally with arguments, in which case the wrapped function is invoked directly.CaveatsCurrently, custom operators are not supported in local scopes. This means that you cannot define a custom
operator inside a function definition: If you attempt to do so, the library will raise an exception. This
is a technically demanding task because local scopes in python are far less dynamic than global scopes.
(This is the same reason why you can't usefrom module import *inside a function definition.)Hoopy is implemented entirely using Python (making maximal use of the builtin parser and tokenizer).
Given that static typing in Python is optional, Hoopy implements custom operators are using pure-Python
dynamic dispatch. This adds some overhead to custom operators that wouldn't be present if they were a
first-class language feature.Hoopy's error reporting could be improved. Currently, the AST transformation does not attempt to match the transformed code's spans
to the relevant positions in source code.Hoopy does its best to ensure that code preprocessed by the library that'snotusing any of its features
remains semantically equivalent to its unprocessed form. This is currently tracked via unit tests running Hoopy
against selected modules within the Python standard library. Future plans include incorporating the builtin
(python3 -m test) tests directly into afullypreprocessed copy of the standard library.Oh, and of course: Please don't use this in production code.DevelopmentTo begin, install the dependencies and set up the development environment. Please run all the unit tests
before submitting any code-related pull requests!$poetryinstall
$poetryrunpre-commitinstallSpecial thanksThanks to @Niki4tap for reviving this project from the ashes and showing that there is a way to sidestep thecpythonbugthat I had previously thought made this library impossible! Hoopy lives again 🕊️