{"id":13439779,"url":"https://github.com/ludat/hado-rs","last_synced_at":"2025-08-18T16:33:14.625Z","repository":{"id":62439897,"uuid":"63474175","full_name":"ludat/hado-rs","owner":"ludat","description":null,"archived":false,"fork":false,"pushed_at":"2018-11-08T01:17:36.000Z","size":19,"stargazers_count":44,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-01T11:15:30.921Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","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/ludat.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-07-16T08:50:11.000Z","updated_at":"2024-04-10T15:37:07.000Z","dependencies_parsed_at":"2022-11-01T22:15:59.105Z","dependency_job_id":null,"html_url":"https://github.com/ludat/hado-rs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ludat%2Fhado-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ludat%2Fhado-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ludat%2Fhado-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ludat%2Fhado-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ludat","download_url":"https://codeload.github.com/ludat/hado-rs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230252772,"owners_count":18197285,"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-07-31T03:01:17.018Z","updated_at":"2024-12-18T10:19:15.416Z","avatar_url":"https://github.com/ludat.png","language":"Rust","funding_links":[],"categories":["Libraries","Rust","库","库 Libraries"],"sub_categories":["Macro","宏","宏 Macro"],"readme":"# hado\n\nMonadic haskell-like expressions brought to rust via the `hado!` macro\n\n## What?\n\nA little macro for writing haskell-like do expressions without too much ceremony\n\n## Why?\n\nRust is a very explicit language when it comes to errors (via Option and Result\ntypes) but it can get cumbersome to handle all of them, so this library brings\nthe composable monad pattern from haskell like languages.\n\nLet's show an example: We will try to do simple math and compose possible\nfailures.\n\nFirst we define our return type, this type will represent the failure or success\nof the functions.\n\n```rust\ntype MathResult = Option\u003cf64\u003e;\n```\n\n- Division: if the divisor is 0 we fail\n\n```rust\n  fn div(x: f64, y: f64) -\u003e MathResult {\n      if y == 0.0 {\n          None\n      } else {\n          Some(x / y)\n      }\n  }\n```\n\n- Square root: if we get a negative number, we fail\n\n```rust\n  fn sqrt(x: f64) -\u003e MathResult {\n      if x \u003c 0.0 {\n          None\n      } else {\n          Some(x.sqrt())\n      }\n  }\n```\n\n- Logarithm: again if we get a negative number, we fail\n\n```rust\n  fn ln(x: f64) -\u003e MathResult {\n      if x \u003c 0.0 {\n          None\n      } else {\n          Some(x.ln())\n      }\n  }\n```\n\nNow we want to get two numbers, divide them, get the sqrt of the result and then\nget the logarithm of the last result\n\n## The naive way\n\n```rust\n  fn op(x: f64, y: f64) -\u003e MathResult {\n      let ratio = div(x, y);\n      if ratio == None {\n          return None\n      };\n      let ln = ln(ratio.unwrap());\n      if ln == None {\n          return None\n      };\n      return sqrt(ln.unwrap())\n  }\n```\n\nEven though this code works it's hard to scale, and it isn't idiomatic rust, it\nlooks more like code you'd see in Java where `None` is `NULL`.\n\n## The better way\n\n```rust\n  fn op(x: f64, y: f64) -\u003e MathResult {\n      match div(x, y) {\n          None =\u003e None,\n          Ok(ratio) =\u003e match ln(ratio) {\n              None =\u003e None,\n              Ok(ln) =\u003e sqrt(ln),\n          },\n      }\n  }\n```\n\nThis example is more rustic but it still looks like too much noise, and still\nit's very hard to scale\n\n## The FP way\n\n```rust\n  fn op(x: f64, y: f64) -\u003e MathResult {\n      div(x, y).and_then(|ratio|\n      ln(ratio).and_then(|ln|\n      sqrt(ln)))\n  }\n```\n\nThis way look almost like the special thing that we want to do but those\n`and_then` and closures seem unnecessary\n\n## The hado macro way\n\n```rust\n  fn op(x: f64, y: f64) -\u003e MathResult {\n      hado! {\n          ratio \u003c- div(x, y);\n          ln \u003c- ln(ratio);\n          sqrt(ln)\n      }\n  }\n```\n\n\nHere we have a very obvious way of declaring out intent without no sign of error\nhandling of any kind, we needed to add a trait `Monad` to Option (which is\nalready defined by default in this library)\n\n## Error type agnostic\n\nNow some more fancy stuff, you may be thinking `but what about the try! macro,\nit would certainly make things better, right?` and my answer would be yes but\nthe try macro _only_ works on the `Result` type so there is no way of changing\nthe type of the error (or use it with our Option based functions).\n\n\nBut now we need to know what was the error that made of computation fail. So we\nchange the `MathResult` alias to be a Result of f64 or a custom type `MathError`\n\n```rust\n  #[derive(Debug)]\n  pub enum MathError {\n      DivisionByZero,\n      NegativeLogarithm,\n      NegativeSquareRoot,\n  }\n\n  type MathResult = Result\u003cf64, MathError\u003e;\n```\n\nSo now we need to change each function because now all the `None` and `Some`\nconstructors are a type error\n\nFor example div turns into\n\n```rust\n  fn div(x: f64, y: f64) -\u003e MathResult {\n      if y == 0.0 {\n          Err(MathError::DivisionByZero)\n      } else {\n          Ok(x / y)\n      }\n  }\n```\n\nnote that the only changes are:\n\n- ~~None~~ `Err(MathError::DivisionByZero)`\n- ~~Some~~ `Ok (x / y)`\n\nand now we check out the op function for each implementation:\n\n* *Naive way*: Change all the constructors, the failure checker, luckily rust's\n     type inference saves us from changing too many type declarations.\n* *Better way*: Slightly better, same constructors, but failure checkers are\n     replaced by match statements but still very verbose.\n* *FP way*: a lot better, the only worry we have is that the new error type has\n     some sort of ~and_then~ and everything else should work.\n* *hado way*: Similar to the last, but now there is nothing to change, the\n     computation has no concern over the failure framework.\n\n# How can I use it?\n\nThe macro can do some basic stuff based on a trait defined inside the crate\n*Monad* which has implementations for Option and Result by default\n\nLet's take some examples from the rust book and rust by example and translate\nthem into hado format.\n\n## [Example from Result type reference](https://doc.rust-lang.org/std/result/#the-try-macro)\n\nHere is the original try based error handling with early returns\n\n```rust\n  fn write_info(info: \u0026str) -\u003e io::Result\u003c()\u003e {\n      let mut file = try!(File::create(\"my_best_friends.txt\"));\n      println!(\"file created\");\n      try!(file.write_all(format!(\"rating: {}\\n\", info.rating).as_bytes()));\n      Ok(())\n  }\n```\n\nAnd here is the hado based\n\n```rust\n  fn hado_write_info(string: \u0026str) -\u003e io::Result\u003c()\u003e {\n      hado!{\n          mut file \u003c- File::create(\"my_best_friends.txt\");\n          println!(\"file created\");\n          file.write_all(format!(\"string: {}\\n\", string).as_bytes())\n      }\n  }\n```\n\nNote that the ign keyword is special, it means that the inner value is discarded\nbut in the case of failure the whole expressions will short circuit into that\nerror. Since there is no arrow (`\u003c-`) the return of the `println` is completely\ndiscarded so you can have any non failing statements in there (including `let`\nand `use`)\n\n## Multi parameter constructor\n\nlet's say we have a Foo struct that has a new method\n\n```rust\n  fn new(a: i32, b: f64, s: String) -\u003e Foo {\n      Foo {\n          a: i32,\n          b: f64,\n          s: String\n      }\n  }\n```\n\nCreate a Option constructor from a normal constructor with minimal hassle\n\n```rust\n  fn opt_new(a: Option\u003ci32\u003e, b: Option\u003cf64\u003e, s: Option\u003cString\u003e) -\u003e Option\u003cFoo\u003e {\n      a \u003c- a;\n      b \u003c- b;\n      s \u003c- s;\n      ret new(a, b, s)\n  }\n```\n\nNote that only by changing the type signature you can change Option to any other\nmonadic type.\n\nYou can also do some custom validation without much boilerplate\n\n```rust\n  fn opt_new(a: i32, b: f64, s: String) -\u003e Option\u003cFoo\u003e {\n      a \u003c- validate_a(a);\n      b \u003c- validate_b(b);\n      s \u003c- validate_s(s);\n      ret new(a, b, s)\n  }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fludat%2Fhado-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fludat%2Fhado-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fludat%2Fhado-rs/lists"}