{"id":15471722,"url":"https://github.com/thadeu/zx-monads","last_synced_at":"2025-04-06T01:42:37.382Z","repository":{"id":196543307,"uuid":"696450519","full_name":"thadeu/zx-monads","owner":"thadeu","description":"Monads for Ruby","archived":false,"fork":false,"pushed_at":"2023-10-06T13:42:51.000Z","size":35,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-19T01:15:57.679Z","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thadeu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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-09-25T19:10:33.000Z","updated_at":"2023-09-25T19:12:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"2d4e652e-96bc-442b-835e-8ce8acb80eac","html_url":"https://github.com/thadeu/zx-monads","commit_stats":{"total_commits":17,"total_committers":1,"mean_commits":17.0,"dds":0.0,"last_synced_commit":"c1c27940f2d5dcc76dffad11d88a99ebc8b3c706"},"previous_names":["thadeu/zx-monads"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thadeu%2Fzx-monads","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thadeu%2Fzx-monads/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thadeu%2Fzx-monads/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thadeu%2Fzx-monads/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thadeu","download_url":"https://codeload.github.com/thadeu/zx-monads/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247423494,"owners_count":20936622,"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":"2024-10-02T02:21:28.364Z","updated_at":"2025-04-06T01:42:37.326Z","avatar_url":"https://github.com/thadeu.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003e🔃 Zx::Monads\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\u003ci\u003eFP Monads for Ruby\u003c/i\u003e\u003c/p\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://rubygems.org/gems/zx-monads\"\u003e\n    \u003cimg alt=\"Gem\" src=\"https://img.shields.io/gem/v/zx-monads.svg\"\u003e    \n  \u003c/a\u003e\n\n  \u003ca href=\"https://github.com/thadeu/zx-monads/actions/workflows/ci.yml\"\u003e\n    \u003cimg alt=\"Build Status\" src=\"https://github.com/thadeu/zx-monads/actions/workflows/ci.yml/badge.svg\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\n## Motivation\n\nBecause in sometimes, we need to handling a safe value for our objects. This gem simplify this work.\n\n## Documentation \u003c!-- omit in toc --\u003e\n\nVersion    | Documentation\n---------- | -------------\nunreleased | https://github.com/thadeu/zx-monads/blob/main/README.md\n\n## Table of Contents \u003c!-- omit in toc --\u003e\n  - [Installation](#installation)\n  - [Usage](#usage)\n\n## Compatibility\n\n| kind           | branch  | ruby               |\n| -------------- | ------- | ------------------ |\n| unreleased     | main    | \u003e= 2.7.6, \u003c= 3.2.x |\n\n## Installation\n\nUse bundle\n\n```ruby\nbundle add zx-monads\n```\n\nor add this line to your application's Gemfile.\n\n```ruby\ngem 'zx-monads'\n```\n\nand then, require module\n\n```ruby\nrequire 'zx'\n```\n\n## Configuration\n\nWithout configuration, because we use only Ruby. ❤️\n\n## Usage\n\nHow to use in my codebase?\n\n```rb\nclass Order\n  include Zx # include all Zx library.\nend\n\nclass Order\n  include Zx::Maybeable\nend\n\nclass ProcessOrder \u003c Zx::Steps\n  # include Zx::Maybeable included now!\nend\n```\n\n### Available public methods\n\n```md\n#type -\u003e Returns maybe type\n#some? -\u003e Returns boolean \n#none? -\u003e Returns boolean \n#unwrap -\u003e Returns value unwrapped\n#or(value) -\u003e Returns unwrap or value\n#\u003e\u003e(other) -\u003e Forward to another Maybe\n#fmap -\u003e Create an step and wrap new value\n#map(:key) -\u003e Same the fmap, but receive another parameters\n#map(\u0026:method) -\u003e Same the map, but respond to method\n#map {} -\u003e Same the the map, but receive an block\n#map!{} -\u003e Same the map, but change to new value\n#apply!{} -\u003e Same the map! but more legible\n#dig(keys) -\u003e Get values using keys like Hash#dig\n#dig!(keys) -\u003e Same them dig, but return unwrap\n#match(some:, none:) -\u003e Receive callables and associate them\n#on_success{} -\u003e Only when Some\n#on_failure{} -\u003e Only then None\n#on(:success|:failure){}\n```\n\n### ZX::Maybe\n\n```ruby\nresult = Zx::Maybe[1] # or Maybe.of(1)\n```\n\n```ruby\nresult = Zx::Maybe[nil] # or Maybe.of(nil)\n```\n\n```ruby\nresult = Zx::Maybe[1].map{ _1 + 2}\n# -\u003e Some(3)\n```\n\n```ruby\nresult = Zx::Maybe[nil].map{ _1 + 2}\n# -\u003e None\n```\n\n```ruby\nresult = Zx::Maybe.of(1).or(2)\nresult.or(2) # 1\n```\n\n```ruby\nresult = Zx::Maybe.of(' ').or(2)\nresult.or(2) # 2\n```\n\n```ruby\nresult = Zx::Maybe.of(' ').or(2)\nresult.or(2) # 2\n```\n\n```ruby\norder = {\n  shopping: {\n    banana: {\n      price: 10.0\n    }\n  }\n}\n\nprice =  Zx::Maybe[order]\n  .map { _1[:shopping] }\n  .map { _1[:banana] }\n  .map { _1[:price] }\n\n# -\u003e Some(10.0)\n\n# or using #dig\n\nprice =  Zx::Maybe[order].dig(:shopping, :banana, :price)\n# -\u003e Some(10.0)\n\nprice_none =  Zx::Maybe[order].dig(:shopping, :banana, :price_non_exists)\n# -\u003e None\n\nprice_or =  Zx::Maybe[order].dig(:shopping, :banana, :price_non_exists).or(10.0)\n# -\u003e Some(10.0)\n```\n\n```rb\nclass Response\n  attr_reader :body\n\n  def initialize(new_body)\n    @body = Zx::Maybe[new_body]\n  end\n\n  def change(new_body)\n    @body = Zx::Maybe[new_body]\n  end\nend\n\nresponse = Response.new(nil)\nexpect(response.body).to be_none\n\nresponse.change({ status: 200 })\nexpect(response.body).to be_some\n\nresponse_status = response.body.match(\n  some: -\u003e(body) { Zx::Maybe[body].map { _1.fetch(:status) }.unwrap },\n  none: -\u003e {}\n)\n```\n\n**Use case, when use to parse response stringify json**\n\n```rb\ndump = JSON.dump({ status: { code: '300' } })\n\nresponse = Response.new(dump) # It's receive an JSON stringified\n\nmodule StatusCodeUnwrapModule\n  def self.call(body)\n    Zx::Maybe[body]\n      .map{ JSON(_1, symbolize_names: true) }\n      .dig(:status, :code)\n      .apply(\u0026:to_i)\n      .unwrap\n  end\nend\n\nresponse_status = response.body.match(\n  some: StatusCodeUnwrapModule,\n  none: -\u003e { 400 }\n)\n  \nexpect(response_status).to eq(300)\n```\n\nYou can use `\u003e\u003e` to compose many callables, like this.\n\n```rb\nsum = -\u003e(x) { Zx::Maybe::Some[x + 1] }\n\nsubtract = -\u003e(x) { Zx::Maybe::Some[x - 1] }\n\nresult = Zx::Maybe[1] \u003e\u003e \\\n  sum \u003e\u003e \\\n  subtract\n\nexpect(result.unwrap).to eq(1)\n```\n\nIf handle None, no worries.\n\n```rb\nsum = -\u003e(x) { Zx::Maybe::Some[x + 1] }\n\nsubtract = -\u003e(_) { Zx::Maybe::None.new }\n\nresult = Zx::Maybe[1] \\\n  \u003e\u003e sum \\\n  \u003e\u003e subtract\n\nexpect(result.unwrap).to be_nil\n```\n\n```rb\nclass Order\n  def self.sum(x)\n    Zx::Maybe[{ number: x + 1 }]\n  end\nend\n\nresult = Order.sum(1)\n  .dig(:number)\n  .apply(\u0026:to_i)\n\nexpect(result.unwrap).to be(2)\n```\n\n### Zx::Maybe::Some\n\n```rb\nclass Order\n  include Zx::Maybeable\n\n  def self.sum(x)\n    new.sum(x)\n  end\n\n  def sum(x)\n    Some[{ number: x + 1 }]\n  end\nend\n\nresult = Order.sum(1)\n  .dig(:number)\n  .apply(\u0026:to_i)\n\nexpect(result.unwrap).to be(2)\n```\n\n### Zx::Maybe::None\n\n```rb\nclass Order\n  include Zx::Maybeable\n\n  def self.sum(x)\n    new.sum(x)\n  end\n\n  def sum(x)\n    Try {{ number: x + ' ' }}\n  end\nend\n\nresult = Order.sum(1)\nnumber = result.dig(:number).apply(\u0026:to_i)\n\nexpect(result).to be_none\nexpect(result).to be_a(Maybe::None)\nexpect(number.unwrap).to be(0) # nil.to_i == 0\n```\n\n### Zx::Maybe::Try\n\n\u003e Only included or inherited!\n\n```rb\nclass Order\n  include Zx::Maybeable\n\n  def self.sum(x)\n    new.sum(x)\n  end\n\n  def sum(x)\n    Try {{ number: x + 1 }}\n  end\nend\n\nresult = Order.sum(1)\n  .dig(:number)\n  .apply(\u0026:to_i)\n\nexpect(result.unwrap).to be(2)\n```\n\nWith default value, in None case.\n\n```rb\nclass Order\n  include Zx::Maybeable\n\n  def self.sum(x)\n    new.sum(x)\n  end\n\n  def sum(x)\n    Try(2) {{ number: x + ' ' }}\n  end\nend\n\nresult = Order.sum(1)\n  .dig(:number)\n  .apply(\u0026:to_i)\n\nexpect(result.unwrap).to be(2)\n```\n\n```rb\nclass Order\n  include Zx::Maybeable\n\n  def self.sum(x)\n    new.sum(x)\n  end\n\n  def sum(x)\n    Try(or: 1000) {{ number: x + ' ' }}\n  end\nend\n\nresult = Order.sum(1).dig(:number).apply(\u0026:to_i)\n\nexpect(result.unwrap).to be(1000)\n```\n\n### Zx::Steps\n\n```rb\nclass OrderStep \u003c Zx::Steps\n  def initialize(x = nil)\n    @x = x\n  end\n\n  step :positive?\n  step :apply_tax\n  step :divide\n\n  def positive?\n    return None unless total.is_a?(Integer) || total.is_a?(Float)\n    return None if total \u003c= 0\n\n    Some total\n  end\n\n  def apply_tax\n    Try { @x -= (@x * 0.1) }\n  end\n\n  def divide\n    Try { @x /= 2 }\n  end\n\n  def total\n    @x\n  end\nend\n```\n\n```rb\norder = OrderStep.new(20)\n\norder.call\n  .map { |n| n + 1 }\n  .on_success { |some| expect(some.unwrap).to eq(10) }\n  .on_failure { |none| expect(none.or(0)).to eq(0) }\n```\n\n```rb\norder = OrderStep.new(20)\n\norder.call\n  .map { |n| n + 1 }\n  .on(:success) { |some| expect(some.unwrap).to eq(10) }\n  .on(:failure) { |none| expect(none.or(0)).to eq(0) }\n```\n\n```rb\norder = OrderStep.new(-1)\n\norder.call\n  .on_success { raise }\n  .on_failure { |none| expect(none.or(0)).to eq(0) }\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/thadeu/zx-monads. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/thadeu/zx-monads/blob/master/CODE_OF_CONDUCT.md).\n\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthadeu%2Fzx-monads","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthadeu%2Fzx-monads","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthadeu%2Fzx-monads/lists"}