Last active
April 28, 2023 23:21
-
-
Save ruxo/d60871dc265d12276b0ccccb479a6de3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [package] | |
| name = "io_monad_guessing_game" | |
| version = "0.1.0" | |
| authors = ["Ruxo Zheng <me@ruxoz.net>"] | |
| edition = "2021" | |
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
| [dependencies] | |
| anyhow = "1" | |
| tinyrand = "0.5" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use anyhow::{Result, anyhow}; | |
| use std::{io::{stdin, stdout, Write}, time::SystemTime}; | |
| use tinyrand::{Rand, Seeded, StdRand}; | |
| trait IO<A> { | |
| fn run(&mut self) -> Result<A>; | |
| } | |
| fn bind<A, B, F, R>(mut ma: impl IO<A>, f: F) -> impl IO<B> | |
| where | |
| R: IO<B>, | |
| F: Fn(&A) -> R + 'static, | |
| { | |
| move || ma.run().and_then(|ref v| f(v).run()) | |
| } | |
| fn bindr<A, B, F>(mut ma: impl IO<A>, f: F) -> impl IO<B> | |
| where | |
| F: Fn(&A) -> Result<B> + 'static, | |
| { | |
| move || ma.run().and_then(|ref v| f(v)) | |
| } | |
| #[allow(dead_code)] | |
| fn map<A, B, F>(mut ma: impl IO<A>, f: F) -> impl IO<B> | |
| where | |
| F: Fn(&A) -> B + 'static, | |
| { | |
| move || ma.run().map(|ref v| f(v)) | |
| } | |
| fn retry<A>(mut ma: impl IO<A>) -> impl IO<A> { | |
| move || { | |
| loop { | |
| match ma.run() { | |
| v @ Ok(_) => return v, | |
| Err(_) => () | |
| } | |
| } | |
| } | |
| } | |
| impl<A, F> IO<A> for F | |
| where | |
| F: FnMut() -> Result<A>, | |
| { | |
| fn run(&mut self) -> Result<A> { | |
| (self)() | |
| } | |
| } | |
| fn get_random(max: u32) -> impl IO<u32> { | |
| move || { | |
| let seed = SystemTime::now() | |
| .duration_since(SystemTime::UNIX_EPOCH) | |
| .unwrap() | |
| .as_secs(); | |
| let mut rand = StdRand::seed(seed); | |
| Ok(rand.next_lim_u32(max)) | |
| } | |
| } | |
| fn read_line() -> impl IO<String> { | |
| || { | |
| let mut s = String::new(); | |
| let len = stdin().read_line(&mut s)?; | |
| s.truncate(len - 1); | |
| Ok(s) | |
| } | |
| } | |
| fn write_text(text: String) -> impl IO<()> { | |
| move || { | |
| stdout().write_all(text.as_bytes())?; | |
| stdout().flush()?; | |
| Ok(()) | |
| } | |
| } | |
| fn writeln_text(text: String) -> impl IO<()> { | |
| move || { | |
| stdout().write_all(text.as_bytes())?; | |
| stdout().write(b"\r\n")?; | |
| stdout().flush()?; | |
| Ok(()) | |
| } | |
| } | |
| fn main() -> Result<()> { | |
| let target = get_random(100); | |
| let mut program = bind(target, |target| play(*target)); | |
| program.run() | |
| } | |
| fn play(target: u32) -> impl IO<()> { | |
| let t = write_text("Guess: ".to_string()); | |
| let r = bind(t, |_| bindr(read_line(), |s| Ok(s.parse()?))); | |
| let guessing = bind(r, move |guess| { | |
| let guess: u32 = *guess; | |
| let print_text = print_result(target, guess); | |
| bind(print_text, move |_| get_guess_result(target, guess)) | |
| }); | |
| retry(guessing) | |
| } | |
| fn print_result(target: u32, guess: u32) -> impl IO<()> { | |
| if guess < target { writeln_text("Too small".to_string()) } | |
| else if guess > target { writeln_text("Too big".to_string()) } | |
| else { writeln_text(format!("Congrat, it's {guess}!")) } | |
| } | |
| fn get_guess_result(target: u32, guess: u32) -> impl IO<()> { | |
| move || { | |
| if guess == target { Ok(()) } | |
| else { Err(anyhow!("Guess incorrect")) } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment