trait Console[F[_]] {
def readLine: F[String]
def printLine(s: String): F[Unit]
}
// I know that all this function can do is call `readLine`.
// What's more, it can only do it exactly (usefully) once.
// Anything else would be a reason to talk
def foo[F[_] : Console]: F[String] = ???
// This could call `readLine` *or* `printLine`
// It can still only do it exactly (usefully) once.
// I've no guarantee that the `F[String]` I get back is from from `readLine`
// Anything else would be a reason to talk
def foo[F[_] : Functor : Console]: F[String] = ???
// This could call `readLine` or `printLine` many times
// It could also now call `readLine` or `printLine` zero times
// It's not able to respond to user input, e.g. greeting a user by their name
// Anything else would be a reason to talk
def foo[F[_] : Applicative : Console]: F[String] = ???
// This can do everything that the previous can do and more...
// Now we can respond to user input
def foo[F[_] : Monad : Console]: F[String] = ???
Compare to:
trait Console {
def readLine: IO[String]
def println(s: String): IO[Unit]
}
// What can I say about what this does? Or doesn't do? Or *shouldn't* do?
// What would the implementation need to do to provoke a conversation?
def foo(c: Console): IO[String] = ???