# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['funml', 'funml.data']

package_data = \
{'': ['*']}

install_requires = \
['typing-extensions>=4.4.0,<5.0.0']

setup_kwargs = {
    'name': 'funml',
    'version': '0.2.0',
    'description': 'A collection of utilities to help write python as though it were an ML-kind of functional language like OCaml',
    'long_description': '# FunML\n\n[![PyPI version](https://badge.fury.io/py/funml.svg)](https://badge.fury.io/py/funml) ![CI](https://github.com/sopherapps/funml/actions/workflows/CI.yml/badge.svg)\n\nA collection of utilities to help write python as though it were an ML-kind of functional language like OCaml\n\n**The API is still unstable. Use at your own risk.**\n\n---\n\n**Documentation:** [https://sopherapps.github.io/funml](https://sopherapps.github.io/funml)\n\n**Source Code:** [https://github.com/sopherapps/funml](https://github.com/sopherapps/funml)\n\n--- \n\nMost Notable Features are:\n\n1. Immutable data structures like enums, records, lists\n2. Piping outputs of one function to another as inputs. That\'s how bigger functions are created from smaller ones.\n3. Pattern matching for declarative conditional control of flow instead of using \'if\'s\n4. Error handling using the `Result` monad, courtesy of [rust](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html).\n   Instead of using `try-except` all over the place, functions return \n   a `Result` which has the right data when successful and an exception if unsuccessful. \n   The result is then pattern-matched to retrieve the data or react to the exception.\n5. No `None`. Instead, we use the `Option` monad, courtesy of [rust](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html?highlight=option#the-option-enum-and-its-advantages-over-null-values).\n   When an Option has data, it is `Option.SOME`, or else it is `Option.NONE`. \n   Pattern matching helps handle both scenarios.\n\n## Dependencies\n\n- [python 3.7+](https://docs.python.org/)\n\n## Getting Started\n\n- Ensure you have python 3.7 and above installed.\n- Install `FunML`\n\n```shell\npip install funml\n```\n\n- Add the following code in `main.py`\n\n```python\nfrom datetime import date\n\nimport funml as ml\n\n\ndef main():\n    """Main program"""\n\n    """\n    Data Types:\n    ===\n    \n    Using the `Enum` base class and the \n    `@record` decorator, one can create custom\n    data types in form of Enums and records respectively.\n    """\n\n    class Date(ml.Enum):\n        January = date\n        February = date\n        March = date\n        April = date\n        May = date\n        June = date\n        July = date\n        August = date\n        September = date\n        October = date\n        November = date\n        December = date\n\n    @ml.record\n    class Color:\n        r: int\n        g: int\n        b: int\n        a: int\n\n    """\n    Expressions\n    ===\n    \n    The main construct in funml is the expression.\n    As long as anything is an expression, it can be piped \n    i.e. added to a pipeline.\n    \n    Anything can be turned into an expression using\n    `funml.val`.\n    functions, static values, variables, name it.\n    \n    Expressions are the building blocks for more expressions.\n    Combining multiple expressions creates new expressions\n    \n    It may have:\n    \n    - `ml.Result`, `ml.Option` and their helpers like `ml.is_ok`, `ml.if_ok`\n    - `IList` and its helpers `ireduce`, `imap`, `ifilter` etc.\n    - `Enum`\'s, `Record`\'s\n    - pattern matching with `ml.match().case(...)`\n    - lambda functions wrapped in `ml.val` to make them expressions\n    - Even piping with the `>>` to move data from LEFT to RIGHT through a number of expressions\n    etc.\n    """\n\n    """\n    Primitive Expressions\n    ---\n    \n    We can start with a few primitive expressions.\n    These we will use later to build more complex expressions.\n    \n    A typical primitive expression is `ml.val(<lambda function>)`\n    But one can also wrap functions/classes from external modules\n    \n    e.g. \n    MlDbConnection = ml.val(DbConnection)\n    # then later, use it as though it was a funml expression.\n    conn = (\n            ml.val(config) \n            >> MlDbConnection\n            >> ml.execute())\n            \n    We have some builtin primitive expressions like\n    - ml.val\n    - ml.let \n    - ml.match\n    - ml.execute\n    - ml.ireduce\n    - ml.ifilter\n    - ml.imap\n    - ml.if_ok\n    - ml.is_ok\n    - ml.if_err\n    - ml.is_err\n    - ml.if_some\n    - ml.is_some\n    - ml.if_none\n    - ml.is_none\n    """\n    unit = ml.val(lambda v: v)\n    is_even = ml.val(lambda v: v % 2 == 0)\n    mul = ml.val(lambda x, y: x * y)\n    superscript = ml.val(lambda num, power: num**power)\n    get_month = ml.val(lambda value: value.month)\n    is_num = ml.val(lambda v: isinstance(v, (int, float)))\n    is_exp = ml.val(lambda v: isinstance(v, BaseException))\n\n    """\n    Higher-level Expressions\n    ---\n    \n    Here we combine the primitive expressions into more\n    complex ones using:\n    \n    - normal function calls \n      e.g. `if_else(some_stuff)` where `if_else` is a primitive expression\n    - pipes `>>`\n      pipes let one start with data then define the steps that operate on the\n      data.\n      e.g. `output = records >> remove_nulls >> parse_json >> ml.execute()`\n    - chaining primitives that have methods on their outputs that return expressions.\n      e.g. `output = ml.match(data).case(1, do=...).case(2, do=...).case(3, ...)`\n      \n    We can combine these complex expressions into even more complex ones\n    to infinite complexity.\n    \n    That is the main thing about functional programming i.e.\n    composing simpler functions into more complex functions \n    to an indefinite level of complexity BUT while keeping the\n    complex functions readable and predictable (pure)\n    \n    NOTE:\n    ---\n    Avoid calling expressions recursively. Each expression has state\n    and unexpected things happen when hidden state is maintained during\n    recursion.\n    """\n    accum_factorial = ml.val(lambda num, accum: (\n        accum if num <= 0 else accum_factorial(num - 1, num * accum)\n    ))\n    cube = ml.let(int, power=3) >> superscript\n    factorial = ml.val(lambda x: accum_factorial(x, 1))\n    get_item_types = ml.ireduce(lambda x, y: f"{type(x)}, {type(y)}")\n    num_type_err = ml.val(\n        lambda *args: TypeError(f"expected numbers, got {get_item_types(args)}")\n    )\n    is_seq_of_nums = ml.ireduce(lambda x, y: x and is_num(y), True)\n    to_result = ml.val(lambda v: ml.Result.ERR(v) if is_exp(v) else ml.Result.OK(v))\n\n    try_multiply = ml.val(\n        lambda x, y: num_type_err(x, y) if is_seq_of_nums([x, y]) else mul(x, y)\n    ) >> to_result\n\n    result_to_option = ml.if_ok(ml.Option.SOME, strict=False) >> ml.if_err(\n        lambda *args: ml.Option.NONE, strict=False\n    )\n    to_date_enum = ml.val(\n        lambda v: (\n            ml.match(v.month)\n            .case(1, do=ml.val(Date.January(v)))\n            .case(2, do=ml.val(Date.February(v)))\n            .case(3, do=ml.val(Date.March(v)))\n            .case(4, do=ml.val(Date.April(v)))\n            .case(5, do=ml.val(Date.May(v)))\n            .case(6, do=ml.val(Date.June(v)))\n            .case(7, do=ml.val(Date.July(v)))\n            .case(8, do=ml.val(Date.August(v)))\n            .case(9, do=ml.val(Date.September(v)))\n            .case(10, do=ml.val(Date.October(v)))\n            .case(11, do=ml.val(Date.November(v)))\n            .case(12, do=ml.val(Date.December(v)))\n        )()\n    )\n    get_month_str = get_month >> (\n        ml.match()\n        .case(1, do=ml.val("JAN"))\n        .case(2, do=ml.val("FEB"))\n        .case(3, do=ml.val("MAR"))\n        .case(4, do=ml.val("APR"))\n        .case(5, do=ml.val("MAY"))\n        .case(6, do=ml.val("JUN"))\n        .case(7, do=ml.val("JUL"))\n        .case(8, do=ml.val("AUG"))\n        .case(9, do=ml.val("SEP"))\n        .case(10, do=ml.val("OCT"))\n        .case(11, do=ml.val("NOV"))\n        .case(12, do=ml.val("DEC"))\n    )\n\n    """\n    Data\n    ===\n    \n    We have a number of data types that are work well with ml\n    \n        - IList: an immutable list, with pattern matching enabled\n        - Enum: an enumerable data type, with pattern matching enabled\n        - Record: a record-like data type, with pattern matching enabled\n        \n    Using our Higher level expressions (and lower level ones if they can),\n    we operate on the data.\n    \n    In order to add data variables to pipelines, we turn them into expressions\n    using `ml.val`\n    \n    e.g. `ml.val(90)` becomes an expression that evaluates to `lambda: 90`\n    """\n    dates = [\n        date(200, 3, 4),\n        date(2009, 1, 16),\n        date(1993, 12, 29),\n        date(2004, 10, 13),\n        date(2020, 9, 5),\n        date(2004, 5, 7),\n        date(1228, 8, 18),\n    ]\n    dates = ml.val(dates)\n    nums = ml.val(ml.l(12, 3, 45, 7, 8, 6, 3))\n    data = ml.l((2, 3), ("hey", 7), (5, "y"), (8.1, 6))\n    blue = Color(r=0, g=0, b=255, a=1)\n\n    """\n    Execution\n    ===\n    \n    To mimic pipelines, we use\n    `>>` as pipe to move data from left to right\n    and `ml.execute()` to execute the pipeline and return \n    the results\n    \n    Don\'t forget to call `ml.execute()` at the end of the\n    pipeline or else you will get just a callable object.\n    \n    It is more like not calling `await` on a function that\n    returns an `Awaitable`.\n    """\n    dates_as_enums = dates >> ml.imap(to_date_enum) >> ml.execute()\n    print(f"\\ndates as enums: {dates_as_enums}")\n\n    print(f"\\nfirst date enum: {dates_as_enums[0]}")\n\n    months_as_str = dates >> ml.imap(get_month_str) >> ml.execute()\n    print(f"\\nmonths of dates as str:\\n{months_as_str}")\n\n    print(f"\\ncube of 5: {cube(5)}")\n\n    factorials_str = (\n        nums\n        >> ml.ifilter(is_even)\n        >> ml.imap(lambda v: f"factorial for {v}: {factorial(v)}")\n        >> ml.ireduce(lambda x, y: f"{x}\\n{y}")\n        >> ml.execute()\n    )\n    print(factorials_str)\n\n    print(f"blue: {blue}")\n\n    data = ml.val(data) >> ml.imap(lambda x: try_multiply(*x)) >> ml.execute()\n    print(f"\\nafter multiplication:\\n{data}")\n\n    data_as_options = ml.val(data) >> ml.imap(result_to_option) >> ml.execute()\n    print(f"\\ndata as options: {data_as_options}")\n\n    data_as_actual_values = (\n        ml.val(data) >> ml.ifilter(ml.is_ok) >> ml.imap(ml.if_ok(unit)) >> ml.execute()\n    )\n    print(f"\\ndata as actual values: {data_as_actual_values}")\n\n\nif __name__ == "__main__":\n    main()\n```\n\n- Run the script\n\n```shell\npython main.py\n```\n\n## Contributing\n\nContributions are welcome. The docs have to maintained, the code has to be made cleaner, more idiomatic and faster,\nand there might be need for someone else to take over this repo in case I move on to other things. It happens!\n\nPlease look at the [CONTRIBUTIONS GUIDELINES](./CONTRIBUTING.md)\n\n## Benchmarks\n\nTBD\n\n## License\n\nLicensed under both the [MIT License](./LICENSE)\n\nCopyright (c) 2023 [Martin Ahindura](https://github.com/tinitto)\n\n## Gratitude\n\n> "...and His (the Father\'s) incomparably great power for us who believe. That power is the same as the mighty strength\n> He exerted when He raised Christ from the dead and seated Him at His right hand in the heavenly realms, \n> far above all rule and authority, power and dominion, and every name that is invoked, not only in the present age but \n> also in the one to come."\n>\n> -- Ephesians 1: 19-21\n\nAll glory be to God.\n\n<a href="https://www.buymeacoffee.com/martinahinJ" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>',
    'author': 'Martin',
    'author_email': 'team.sopherapps@gmail.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/sopherapps/funml',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
