1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/// Do some silly console IO that touches what all I've made
fn main() {
    prompt("How many apples do you have? ")
        .bind(|apples| {
            let apples = apples.unwrap();
            prompt("How many oranges do you have? ")
                .bind(move |oranges| Io::ret((apples.clone(), oranges.unwrap())))
        })
        .bind(|(apples, oranges)| {
            Io::ret((
                apples.parse::<u32>().unwrap(),
                oranges.parse::<u32>().unwrap(),
            ))
        })
        .bind(|(apples, oranges)| {
            put_str("You have ")
                .bind(|_| print(apples + oranges))
                .bind(|_| put_str_ln(" fruits!"))
        });
}

/// A computation that, when performed, does some I/O before returning a value of type `T`.
struct Io<T>(T);

/// Monad properties
///
/// A lot based on https://stackoverflow.com/a/31892905
impl<T> Io<T> {
    /// Do no I/O and return T
    fn ret(t: T) -> Io<T> {
        Io(t)
    }

    ///
    fn bind<S>(self, f: impl Fn(T) -> Io<S>) -> Io<S> {
        f(self.0)
    }
}

/// Write `s` to stdout
fn put_str(s: &str) -> Io<()> {
    Io::ret(s).bind(|s| {
        print!("{s}");
        Io::ret(())
    })
}

/// Write `s` and `'\n'` to stdout
fn put_str_ln(s: &str) -> Io<()> {
    Io::ret(s).bind(|s| {
        println!("{s}");
        Io::ret(())
    })
}

/// Read a single line from stdin
fn get_line() -> Io<std::io::Result<String>> {
    Io::ret(()).bind(|()| {
        let mut buffer = String::new();
        Io::ret(std::io::stdin().read_line(&mut buffer).map(|_| buffer))
    })
}

/// Stdout's fd
fn stdout() -> Io<std::io::Stdout> {
    Io::ret(std::io::stdout())
}

/// Flush an I/O buffer
fn flush(mut buf: impl std::io::Write) -> Io<std::io::Result<()>> {
    Io::ret(buf.flush())
}

/// Write a prompt to stdout, read a line from stdin, and return that line with whitespace trimmed from the ends.
fn prompt(ps1: &str) -> Io<std::io::Result<String>> {
    put_str(ps1)
        .bind(|_| stdout())
        .bind(flush)
        .bind(|_| get_line())
        .bind(|line| Io::ret(line.map(|s| s.trim().into())))
}

/// Write a printable value to the console
fn print<T: std::fmt::Display>(t: T) -> Io<()> {
    Io::ret(t).bind(|t| {
        print!("{t}");
        Io::ret(())
    })
}