Dealing with asynchronous-ness in PureScript

Harry Garrood

Overview

  1. Background: IO without side effects
  2. Background: Quick introduction to PureScript
  3. Asynchronous IO in PureScript

Background: Side effects

  • Any communication a function performs with the rest of the program (or the outside world) other than its parameters or return value.
  • FP = avoiding side effects

What IO looks like without side-effects

  • A data type for representations of IO actions
readFile :: String -> IO String

What IO looks like without side-effects

  • Do notation:
main = do
  putStr "Please enter your name: "
  name <- getLine
  putStrLn ("'" ++ name ++ "'? That's a nice name!")
  putStrLn ("It has " ++ length name ++ " characters.")

Background: PureScript

module Main where

import Prelude
import Control.Monad.Eff.Console (log)

greet :: String -> String
greet name = "Hello, " ++ name ++ "!"

main = log (greet "World")

IO =~ Eff

log :: String -> Eff _ Unit

function log(message) {
  return function() {
    console.log(message)
    return {}
  }
}

Limitations of Eff

  • Synchronous
httpGet :: URL -> Eff _ HttpResponse
  • Callbacks
httpGet :: URL
            -> (HttpResponse -> Eff _ Unit)
            -> Eff _ Unit

Limitations of callbacks

main =
  getFirstArgument (\inputFile -> do
    log ("Reading: " ++ inputFile)
    readFile inputFile (\json ->
      case parseJson json of
        Left err ->
          throw err
        Right data ->
          httpPost (url data) (payload data) (\resp ->
            log resp.body)))

What do we want to write?

main = do
  inputFile <- getFirstArgument
  log ("Reading: " ++ inputFile)
  json <- readFile inputFile
  case parseJson json of
    Left err ->
      throw err
    Right data -> do
      response <- httpPost (url data) (payload data)
      log response.body

Error handling with Aff

main = catchError myProgram (\err -> do
  log "Oops! Something went wrong."
  log "This is a bug. Please report it!"
  log "Error details:"
  log (inspect err))

Error handling with Aff

(<|>) :: forall a. Aff _ a -> Aff _ a -> Aff _ a
        
httpGet "http://foo.com" <|> httpGet "http://bar.com"

attemptGet ["http://foo.com", "http://bar.com"]
  where
  attemptGet domains =
    foldr (<|>) onAllFailed (map httpGet domains)
  onAllFailed =
    throwError (error "Could not GET from any domain")

Running actions in parallel

newtype Par e a = Par (Aff e a)

Par (httpGet "http://foo.com") :: Par _ a

Running actions in parallel

traverse :: forall a b.
  (a -> Par _ b) -> Array a -> Par _ (Array b)

loadAll
  [ "/images/player.png"
  , "/images/wall.png"
  , "/images/enemy.png"
  ]
  where
  loadAll urls =
    runPar (traverse go urls)
  go url =
    Par (httpGet url)