{"id":18830283,"url":"https://github.com/sopherapps/funml","last_synced_at":"2026-01-24T08:30:12.678Z","repository":{"id":65542647,"uuid":"589776039","full_name":"sopherapps/funml","owner":"sopherapps","description":"A collection of utilities to help write python as though it were an ML-kind of functional language like OCaml","archived":false,"fork":false,"pushed_at":"2023-03-20T13:15:07.000Z","size":882,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-03-15T09:52:03.324Z","etag":null,"topics":["functional-programming","pattern-matching","python"],"latest_commit_sha":null,"homepage":"https://sopherapps.github.io/funml/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sopherapps.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-16T22:51:58.000Z","updated_at":"2023-03-22T22:31:02.000Z","dependencies_parsed_at":"2023-06-01T11:30:38.622Z","dependency_job_id":null,"html_url":"https://github.com/sopherapps/funml","commit_stats":{"total_commits":113,"total_committers":1,"mean_commits":113.0,"dds":0.0,"last_synced_commit":"d448293bf7c8ecc84200a93a0e7f9df8b98025f2"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sopherapps%2Ffunml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sopherapps%2Ffunml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sopherapps%2Ffunml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sopherapps%2Ffunml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sopherapps","download_url":"https://codeload.github.com/sopherapps/funml/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239768957,"owners_count":19693760,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","pattern-matching","python"],"created_at":"2024-11-08T01:48:22.087Z","updated_at":"2025-02-20T02:41:08.395Z","avatar_url":"https://github.com/sopherapps.png","language":"Python","funding_links":["https://www.buymeacoffee.com/martinahinJ"],"categories":[],"sub_categories":[],"readme":"# 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 copy import copy\nfrom datetime import date\n\nimport funml as ml\n\n\nclass 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\n@ml.record\nclass Color:\n    r: int\n    g: int\n    b: int\n    a: int\n\n\ndef main():\n    \"\"\"Main program\"\"\"\n\n    \"\"\"\n    Primitive Expressions\n    \"\"\"\n    unit = ml.val(lambda v: v)\n    is_even = ml.val(lambda v: v % 2 == 0)\n    mul = ml.val(lambda args: args[0] * args[1])\n    superscript = ml.val(lambda num, power=1: 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    if_else = lambda check=unit, do=unit, else_do=unit: ml.val(\n        lambda *args, **kwargs: (\n            ml.match(check(*args, **kwargs))\n            .case(True, do=lambda: do(*args, **kwargs))\n            .case(False, do=lambda: else_do(*args, **kwargs))\n        )()\n    )\n\n    \"\"\"\n    High Order Expressions\n    \"\"\"\n    factorial = lambda v, accum=1: (\n        ml.match(v \u003c= 0)\n        .case(True, do=ml.val(accum))\n        .case(False, do=lambda num, ac=0: factorial(num - 1, accum=num * ac)())\n    )\n    # currying expressions is possible\n    cube = superscript(power=3)\n    get_item_types = ml.ireduce(lambda x, y: f\"{type(x)}, {type(y)}\")\n    nums_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 = (\n        if_else(check=is_seq_of_nums, do=mul, else_do=nums_type_err) \u003e\u003e to_result\n    )\n\n    result_to_option = ml.if_ok(ml.Option.SOME, strict=False) \u003e\u003e 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 \u003e\u003e (\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    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    Pipeline Creation and Execution\n    \"\"\"\n    dates_as_enums = dates \u003e\u003e ml.imap(to_date_enum) \u003e\u003e 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 \u003e\u003e ml.imap(get_month_str) \u003e\u003e 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    even_nums_pipeline = nums \u003e\u003e ml.ifilter(is_even)\n    # here `even_nums_pipeline` is a `Pipeline` instance\n    print(even_nums_pipeline)\n\n    factorials_list = (\n        copy(even_nums_pipeline)\n        \u003e\u003e ml.imap(lambda v: f\"factorial for {v}: {factorial(v)}\")\n        \u003e\u003e ml.execute()\n    )\n    # we created a new pipeline by coping the previous one\n    # otherwise we would be mutating the old pipeline.\n    # Calling ml.execute(), we get an actual iterable of strings\n    print(factorials_list)\n\n    factorials_str = (\n        even_nums_pipeline\n        \u003e\u003e ml.imap(lambda v: f\"factorial for {v}: {factorial(v)}\")\n        \u003e\u003e ml.ireduce(lambda x, y: f\"{x}\\n{y}\")\n        \u003e\u003e ml.execute()\n    )\n    # here after calling ml.execute(), we get one string as output\n    print(factorials_str)\n\n    print(f\"blue: {blue}\")\n\n    data = ml.val(data) \u003e\u003e ml.imap(try_multiply) \u003e\u003e ml.execute()\n    print(f\"\\nafter multiplication:\\n{data}\")\n\n    data_as_options = ml.val(data) \u003e\u003e ml.imap(result_to_option) \u003e\u003e ml.execute()\n    print(f\"\\ndata as options: {data_as_options}\")\n\n    data_as_actual_values = (\n        ml.val(data) \u003e\u003e ml.ifilter(ml.is_ok) \u003e\u003e ml.imap(ml.if_ok(unit)) \u003e\u003e 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- For more details, visit the [docs](https://sopherapps.github.io/funml)\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## License\n\nLicensed under both the [MIT License](./LICENSE)\n\nCopyright (c) 2023 [Martin Ahindura](https://github.com/tinitto)\n\n## Gratitude\n\n\u003e \"...and His (the Father's) incomparably great power for us who believe. That power is the same as the mighty strength\n\u003e He exerted when He raised Christ from the dead and seated Him at His right hand in the heavenly realms, \n\u003e far above all rule and authority, power and dominion, and every name that is invoked, not only in the present age but \n\u003e also in the one to come.\"\n\u003e\n\u003e -- Ephesians 1: 19-21\n\nAll glory be to God.\n\n\u003ca href=\"https://www.buymeacoffee.com/martinahinJ\" target=\"_blank\"\u003e\u003cimg src=\"https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png\" alt=\"Buy Me A Coffee\" style=\"height: 60px !important;width: 217px !important;\" \u003e\u003c/a\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsopherapps%2Ffunml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsopherapps%2Ffunml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsopherapps%2Ffunml/lists"}