{"id":15057718,"url":"https://github.com/tk3369/monadfunctions.jl","last_synced_at":"2025-06-22T20:33:58.653Z","repository":{"id":55670837,"uuid":"217822053","full_name":"tk3369/MonadFunctions.jl","owner":"tk3369","description":"Monad implementation in Julia","archived":false,"fork":false,"pushed_at":"2020-12-14T00:44:16.000Z","size":31,"stargazers_count":13,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-10T05:07:58.875Z","etag":null,"topics":["julialang","monads"],"latest_commit_sha":null,"homepage":"","language":"Julia","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/tk3369.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-27T07:53:12.000Z","updated_at":"2023-02-27T19:21:43.000Z","dependencies_parsed_at":"2022-08-15T06:01:14.144Z","dependency_job_id":null,"html_url":"https://github.com/tk3369/MonadFunctions.jl","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tk3369%2FMonadFunctions.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tk3369%2FMonadFunctions.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tk3369%2FMonadFunctions.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tk3369%2FMonadFunctions.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tk3369","download_url":"https://codeload.github.com/tk3369/MonadFunctions.jl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248161273,"owners_count":21057555,"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":["julialang","monads"],"created_at":"2024-09-24T22:10:31.464Z","updated_at":"2025-04-10T05:08:04.521Z","avatar_url":"https://github.com/tk3369.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MonadFunctions\n\n[![Build Status](https://github.com/tk3369/MonadFunctions.jl/workflows/CI/badge.svg)](https://github.com/tk3369/MonadFunctions.jl/actions?query=workflow%3ACI)\n[![codecov.io](http://codecov.io/github/tk3369/MonadFunctions.jl/coverage.svg?branch=master)](http://codecov.io/github/tk3369/MonadFunctions.jl?branch=master)\n\nThis is an experimental package with functions that works with the following types of monads:\n\n- Maybe\n- Either / Result\n- List\n\n## Usage\n\n### Maybe\n\nThe `fmap` function can map over any Maybe monad (either `Just` or `None`).\nIf the input is wrapped as a `Just` object, the output is automatically\nwrapped as well.  `NONE` is a singleton constant of `None`.\n\nUnlike other implementations, a `fmap`'ed function can also take ordinary values\nrather than monads.  It would then apply the function as usual and return\nan ordinary result.  So the result is not _elevated_ to a wrapped monad.\n\n```julia\n1       |\u003e fmap(x -\u003e x + 1)   # 2\njust(1) |\u003e fmap(x -\u003e x + 1)   # Just(2)\nNONE    |\u003e fmap(x -\u003e x + 1)   # NONE\n```\n\nUse `or_else` to switch over to a useful value when `NONE` is encountered.\n\n```julia\n1        |\u003e or_else(2)        # 1\njust(1)  |\u003e or_else(2)        # Just(1)\nNONE     |\u003e or_else(2)        # 2\n```\n\nUse `cata` to execute either left function when the value is nothing or \nthe right function when the value is something useful.\n\n```julia\n1        |\u003e cata(() -\u003e 0, x -\u003e x + 1)     # 2\nNONE     |\u003e cata(() -\u003e 0, x -\u003e x + 1)     # 0\n```\n\nIt is possible to extend to your own `Just` and `None` types by implementing the\n`MaybeTypeTrait`. Note that `Nothing` is given a `IsNone` trait by default.\n\n### Either\n\nThe `Either` type is used to capture either a left or right object. To create an\nEither object, simply use the `left` or `right` function. Use `left_value`\nor`right_value` to extract the wrapped value. Use `is_left` or `is_right` to\ncheck if an object is left or right. There is no discrimination which way is\nbetter.\n\nA special case of `Either` is `Result`, which is used for exception handling.\nUse the `result` constructor to create a `Result` object. By default, any\nsubtypes of `ErrorException` are considered left. Everything else is considered\nright.\n\n```julia\njulia\u003e result(1)\nMonadResult_Value(1)\n\njulia\u003e result(ArgumentError(\"bad input\"))\nMonadResult_Error(ArgumentError(\"bad input\"))\n```\n\nThe convenient `is_left` and `is_right` functions can be used to \ncheck if the object is left or right.  To extract value from the\nobject, use `left_value` or `right_value`.\n\n```julia\njulia\u003e is_right(result(1))\ntrue\n\njulia\u003e is_left(result(ArgumentError(\"bad input\")))\ntrue\n\njulia\u003e right_value(result(1))\n1\n\njulia\u003e left_value(result(1))\nERROR: MethodError: no method matching left_value(::Either{:R,:Result})\n```\n\n### List\n\nA List monad is essentially a 1-dimensional array.  Use the `list` constructor to create a new list monad.  We can `fmap` over all elements, or `flatten` a nested list.\n\n```julia\njulia\u003e m = list(1)\n1-element Array{Int64,1}:\n 1\n\njulia\u003e v = list([1,2,3])\n3-element Array{Int64,1}:\n 1\n 2\n 3\n\njulia\u003e fmap(x -\u003e 2x, v)\n3-element Array{Int64,1}:\n 2\n 4\n 6\n\njulia\u003e flatten([1, [2,3], [[4],[5]]])\n5-element Array{Int64,1}:\n 1\n 2\n 3\n 4\n 5\n```\n\n## More Examples\n\n### Using maybe monad to handle Nothing\n\nMaybe is a monad that either contains something useful or nothing. How is it\nuseful? Sometimes certain functions returns `nothing` rather than throwing\nexception to indicate a negative condition For example:\n\n```julia\nmatch(r\"^a.*\", \"hello\")     # nothing\n```\n\nIt is a bit unfortunate that we must test the condition before using the result:\n\n```julia\nmatched = match(r\"^a.*\", \"hello\")\nresult = if matched !== nothing\n    matched.match * \" world\"\nelse\n    nothing\nend\n```\n\nIf we have the notion of Maybe, then we can do it in a functional style:\n```julia\n\"hello\" |\u003e match(r\"^a.*\") |\u003e matched |\u003e concat(\" world\")\n```\n\nTo make that happen, we can do the following to create composable functions that\nonly take single arguments.\n\n```julia\nBase.match(re::Regex) = Base.Fix1(match, re)\nmatched(rm::RegexMatch) = rm.match\nconcat(s::String) = Base.Fix2(string, s)\n```\n\nIf you don't like type piracy then define your own `match` function or convince\nthe Julia core developers that it is a good addition to the Base library. And,\nthis would work just fine:\n```julia\njulia\u003e \"hello\" |\u003e match(r\"^h.*\") |\u003e matched |\u003e concat(\" world\")\n\"hello world\"\n```\n\nThat's close but this doesn't work for the nothing condition.\n```julia\njulia\u003e \"abc\" |\u003e match(r\"^h.*\") |\u003e matched |\u003e concat(\" world\")\nERROR: MethodError: no method matching matched(::Nothing)\n```\n\nWith the help of `fmap` function, we can make it work:\n```julia\njulia\u003e \"abc\" |\u003e fmap(match(r\"^h.*\")) |\u003e fmap(matched) |\u003e fmap(concat(\" world\")) === nothing\ntrue\n```\n\nThis is getting a little long and hard to read, so we just compose the functions:\n```julia\nprocess = fmap(\n    match(r\"^h.*\"),\n    matched,\n    concat(\" world\")\n)\n\nusing Test\n@test process(\"hello\") == \"hello world\"\n@test process(\"abc\") == nothing\n```\n\nLook ma, it is just a data flow pipeline without any conditional statement.\n\n### Using result monad for exception handling\n\nEither is a monad that contains data on the left side or right side.\nIt is useful to keep track of two scenarios.  For examples:\n\n```julia\njulia\u003e going_to_party = left(\"I am sick\")\nMonadEither_Left(I am sick)\n\njulia\u003e is_left(going_to_party)\ntrue\n\njulia\u003e play_badminton = right(\"this weekend\")\nMonadEither_Right(this weekend)\n\njulia\u003e is_right(play_badminton)\ntrue\n\njulia\u003e right_value(play_badminton)\n\"this weekend\"\n```\n\n`Result` is a monad that is a special case of `Either`. By convention, we stay\non the right track for normal conditions but switch to the left track when we\nencounter an error condition. Once we're on the left track, we stay on the it\nand ignore all computation until the end. As the error condition was captured\nwhen we switch to the left track, we can tell what went wrong when we come out\nof the computation. As you can see, Either monad is useful in handling errors.\n\nA simple example is to run a database query. As part of the process, we need to\nestablish a connection, obtain a database cursor, and then run the query. The\ntrouble is that it may throw an exception at any of the database api calls:\n\n```julia\ntry\n    conn = get_connection(url)\n    cursor = get_cursor(conn)\n    sql = \"select * from somehwere\"\n    return query(cursor, sql)\ncatch ex \n    @error \"Unable to run query due to ex=$ex\"\n    rethrow(ex)\nend\n```\n\nIt would be nice if the error just _flows_ to the end. Without using try-catch\nstatement, we would like to do this:\n\n```julia\n# anonymous function to make it composable\nrun_query(sql) = cursor -\u003e query(cursor, sql)\n\n# error handler\nhandle_query_result(err::LeftEither) = @error(left_value(err))\n\n# result set handler\nhandle_query_result(rs::DataFrame) = \"good job\" \n\n# establish pipeline\nresult = fmap(\n    url,\n    get_connection,\n    get_cursor,\n    run_query(\"select * from sometable\"),\n    handle_query_result\n)\n```\n\nThe returned result from `run_query` is either a good value or an error. We can\nfind out if it's good or bad by calling `is_right` and `is_left` respectively.\nIf needed, we can also dispatch based upon `ResultEither` or `ErrorEither`\ntypes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftk3369%2Fmonadfunctions.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftk3369%2Fmonadfunctions.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftk3369%2Fmonadfunctions.jl/lists"}