Intro to Functional IO

Karl Bielefeldt

Have you ever seen a comment like this?

Real programs need side effects, so I prefer a language that handles them well.

Language A

  • Can run side effects immediately where they are declared.
  • Can throw and catch exceptions.

Language B

  • Can declare side effects separately from where they are run.
  • Can easily run side effects lazily.
  • Can store side effects in variables or collections.
  • Many ways to compose side effects concurrently.
  • Many alternatives for error handling.

Which language better handles side effects?

Hello world


import cats.effect.{IO, IOApp}
import cats.implicits._

object HelloWorld extends IOApp.Simple {
  val run = IO.println("Hello, World!")
}

import Data.Either
import Control.Exception

main :: IO ()
main = putStrLn "Hello, World!"

Get input


IO.readLine.map("Hello, " + _) >>= IO.println

("Hello, " ++) <$> getLine >>= putStrLn

Another way to get input


for {
  name <- IO.readLine
  _    <- IO.println("Hello, " + name)
} yield ()

do
  name <- getLine
  putStrLn ("Hello, " ++ name)

Get name from a web request


fetchName.map("Hello, " + _) >>= IO.println

("Hello, " ++) <$> fetchName >>= putStrLn

Handle errors


fetchName
  .map("Hello, " + _)
  .handleError(_ => "Error fetching name") >>= IO.println

fromRight "Error fetching name" <$>
  tryAny (("Hello, " ++) <$> fetchName)
  >>= putStrLn

tryAny :: IO a -> IO (Either SomeException a)
tryAny = try

Connect to first working server


val connections =
  List("server1", "server2", "server3").map(connect)
val run =
  findFirstSuccess(connections) >>= useConnection

def findFirstSuccess[A](xs: Iterable[IO[A]]): IO[A]

findFirstSuccess connections >>= useConnection
  where
  connections = connect <$> ["server1", "server2", "server3"]

findFirstSuccess :: [IO a] -> IO a

Retry on error


retryUntilSuccess(connect("server1")) >>= useConnection

retryUntilSuccess (connect "server1") >>= useConnection

Advent of Code