{"id":28509121,"url":"https://github.com/atomicobject/monadt","last_synced_at":"2025-07-02T23:32:00.093Z","repository":{"id":56884264,"uuid":"54755130","full_name":"atomicobject/monadt","owner":"atomicobject","description":"Monads \u0026 ADTs in Ruby","archived":false,"fork":false,"pushed_at":"2020-02-23T11:06:33.000Z","size":31,"stargazers_count":27,"open_issues_count":2,"forks_count":1,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-06-05T23:04:09.235Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/atomicobject.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":"2016-03-26T00:34:26.000Z","updated_at":"2023-02-05T10:21:07.000Z","dependencies_parsed_at":"2022-08-20T22:31:11.025Z","dependency_job_id":null,"html_url":"https://github.com/atomicobject/monadt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/atomicobject/monadt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Fmonadt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Fmonadt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Fmonadt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Fmonadt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atomicobject","download_url":"https://codeload.github.com/atomicobject/monadt/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atomicobject%2Fmonadt/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263232729,"owners_count":23434727,"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":[],"created_at":"2025-06-08T22:07:29.111Z","updated_at":"2025-07-02T23:32:00.044Z","avatar_url":"https://github.com/atomicobject.png","language":"Ruby","readme":"# monadt\nMonads \u0026amp; ADTs in Ruby\n\n## Overview \nMonadt supplies basic ADT and Monad support to Ruby. \n\nADTs are defined as Ruby classes and support pattern matching with block callbacks per case.\n\nMonads are defined using Ruby Enumerators to allow for imperative-like structures similar to the syntactic sugar available in Haskell and F#. The Maybe and Choice monads are defined using the ADT methods in Monadt.\n\n## ADTs\n\nDeclare a new ADT with the following syntax:\n```ruby\nrequire 'monadt'\n\nclass MyAdt\n  FooBar = data :a_number\n  Baz = data # no assocated values\n  Else = data :first_data_point, :second_data_point\nend\n```\n\nNow you can pattern match using Monadt's match() method.\n```ruby\ndef some_func(my_adt)\n  match my_adt,\n    with(MyAdt::FooBar) {|a_number| (a_number * 2).to_s },\n    with(MyAdt::Baz) { 'bar bar bar' },\n    with(MyAdt::Else) {|first, second| ((first + second) * 3).to_s }\nend\n```\n\nYou can also match against special class Default for matching all values:\n```ruby\ndef another_func(my_adt)\n  match my_adt,\n    with(MyAdt::Else) {|first, second| first ** second },\n    with(Default) { 1024 }\nend\n```\n\nTo declare a new value use the class constructor:\n```ruby\nMyAdt::FooBar.new 15\n```\n\nIf you need to access the fields directly rather than with pattern matching, you can use the name associated with the data. For example,\n```ruby\nadt_value.a_number\nadt_value.second_data_point\n```\nYou will trigger a NoMethodError if you call a data field for the wrong case.\n\nYou can optionally add several useful helper functions to your ADT by calling\n```ruby\ndecorate_adt MyAdt\n```\n\nYou now have the following methods:\n```ruby\nMyAdt.foo_bar 23 # create a new FooBar (equivalent to MyAdt::FooBar.new 23)\nMyAdt.baz # makes a new Baz\nMyAdt.else 3, 11\n\nadt_value.is_foo_bar? # boolean check for FooBar case\nadt_value.is_baz?\nadt_value.is_else?\n\nadt_value.to_s # sensible defaults like \"FooBar(11)\", \"Baz\", \"Else(34, 99)\"\n```\n\nDecorating your ADTs is optional because you may not want all those helpers, and because I'm sure there is some class name transform case I didn't think of that will break everything in certain edge cases.\n\n### TODO \n\nMake it easy to enforce immutability.\n\n## Monads\n\nMonadt uses Ruby Enumerators to support procedural like syntax for monad control flows. Generally you call\n```ruby\nMonad.\u003cmonad_name\u003e do |m|\n  # object m has two members,\n  # * bind, which performs monadic bind for the specified monad\n  # * return, which performs monadic return for the specified monad\nend\n```\n\n### Built-in monads\n\n* Maybe/Present\n* Either\n* State\n* Reader\n* ReaderStateEither\n\n#### Maybe\n\n```ruby\n# values\nMaybe.just 5\nMaybe.nothing\n\n# example\ndef maybeFunc(x)\n  if x \u003e 10\n    Maybe.nothing\n  else\n    Maybe.just (x - 10)\n  end\nend\n\ndef use_maybe(v)\n  Monad.maybe do |m|\n    x = m.bind (maybeFunc v)\n    y = m.bind (maybeFunc (x*2))\n    m.return (x + y)\n  end\nend\n```\n\nMonadt also includes what I call the \"Present\" monad. It's just like Maybe except nil is interpreted as Nothing and non-nil values are interpreted as Just value.\n\n#### Either\n\n```ruby\n# values\nEither.left \"something went wrong\"\nEither.right 15.0\n\n# ...\ndef use_either(v)\n  Monad.either do |m|\n    x = m.bind (eitherFunc v)\n    y = m.bind (eitherFunc2 x)\n    eitherFunc3 (x + y)\n  end\nend\n```\n\n#### State\n\n```ruby\n# state values are two-element arrays\n# [value, state]\nproc = Monad.state do |m|\n  x = m.bind (returns_a_proc v)\n  y = m.bind (returns_a_proc_2 3 x)\n  m.return (x + y)\nend\n\nvalue, final_state = proc.call(initial_state)\n\n# If you want to run the state function and only care about the final output value, use:\nMonad.run_state(initial_value) do |m|\n   # ...\nend\n```\n\nNote that for the State monad (or any monad whose monadic type is a function), you may find the funkify gem helpful, as it can make Ruby methods partially applicable such that they return a Proc.\n\n#### Reader\n\n```ruby\nproc = Monad.reader do |m|\n  x = m.bind (returns_a_proc_expecting_env 3)\n  y = m.bind (returns_a_proc_expecting_env (x * 2))\n  m.return (y + 10)\nend\nvalue = proc.call(env)\n\n# OR\n\nvalue = Monad.run_reader(env) do |m|\n  # ...\nend\n```\n\n#### ReaderStateEither\n\nThis monad combines Reader, State, and Either, having monadic form (env -\u003e state -\u003e Either\u003cLeftType,[T,state]\u003e).\n\n### Creating new monads\n\nCreate a new monad by defining a class with two static methods, bind and return. They are implemented in standard monad fashion, slightly tweaked for ruby\n\n```ruby\nbind(m_a, \u0026blk) # blk is a block of \"signature\" a -\u003e m_b; this method must return m_b\nreturn(val) # returns m_a\n```\n\n### A note about List Monad\n\nBecause the list monad requires executing the same (a -\u003e m_b) multiple times with different values, it is not currently supported by the Enumerator syntax, as we cannot re-run the same segment of the enumerated block. We're working on coming up with a way around this problem.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatomicobject%2Fmonadt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatomicobject%2Fmonadt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatomicobject%2Fmonadt/lists"}