WalzoneInterview Prep
📞 Interviewing soon? Practice with a realistic AI mock phone interview — it calls you, then scores you. First 15 min FREE →

Haskell · Intermediate · question 24 of 100

How do you handle concurrency in Haskell? Can you mention some libraries that are used for this purpose?

📕 Buy this interview preparation book: 100 Haskell questions & answers — PDF + EPUB for $5

Concurrency in Haskell is achieved through several libraries and abstractions that make concurrent programming easier and more efficient. Some common libraries used for this purpose are ‘Control.Concurrent‘, ‘Control.Monad.Par‘, ‘Control.Parallel‘, ‘async‘, and ‘stm‘. These libraries provide various mechanisms to handle concurrency, such as threads, parallelism, and software transactional memory.

1. Control.Concurrent:

The ‘Control.Concurrent‘ library provides support for Haskell threads. A thread is an independently executing computation that can run concurrently with other threads. Haskell threads have a lightweight nature, unlike OS threads, which makes them suitable for creating large numbers of concurrent tasks.

Here’s an example of using forkIO to create two threads that run concurrently:

import Control.Concurrent
import Control.Monad

main :: IO ()
main = do
  forkIO (forever printA)
  forever printB
  where
    printA = putChar 'A' >> threadDelay 1000000
    printB = putChar 'B' >> threadDelay 1500000

In this example, ‘forkIO‘ creates a new thread that runs a loop printing ’A’ every second, while the main thread runs another loop printing ’B’ every 1.5 seconds.

2. Control.Monad.Par:

The ‘Control.Monad.Par‘ library provides parallelism based on parallel evaluation strategies. It uses a monad called ‘Par‘ that allows you to express parallel computations like this:

import Control.Monad.Par

parallelSum :: Num a => [a] -> a
parallelSum xs = runPar $ do
  let (left, right) = splitAt (length xs `div` 2) xs
  leftVar <- spawn . return $ sum left
  rightVar <- spawn . return $ sum right
  leftVal <- get leftVar
  rightVal <- get rightVar
  return (leftVal + rightVal)

This example computes the sum of a list in parallel by recursively dividing the list into two halves, calculating their sums in parallel, and combining the results.

3. Control.Parallel:

The ‘Control.Parallel‘ library provides ‘par‘ and ‘pseq‘ combinators to introduce parallelism into the evaluation of expressions:

import Control.Parallel

parallelFib :: Int -> Int
parallelFib n
  | n < 10 = fib n
  | otherwise
  = r `par` (s `pseq` (r+s))
  where
    r = parallelFib (n-1)
    s = parallelFib (n-2)

fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

The ‘par‘ combinator introduces parallelism, and ‘pseq‘ ensures that the computation of ‘s‘ happens before the computation of ‘(r+s)‘.

4. async:

The ‘async‘ library provides high-level, intuitive abstractions for asynchronous computations. It allows you to run IO actions concurrently and return their results.

Here’s an example of using async to concurrently fetch two web pages using the http-conduit package:

import Network.HTTP.Conduit (simpleHttp)
import Control.Concurrent.Async

main :: IO ()
main = do
  (page1, page2) <- concurrently (simpleHttp "http://example.com") (simpleHttp "http://example.org")
  print (length page1, length page2)

In this example, the ‘concurrently‘ function runs two IO actions concurrently and returns their results as a tuple.

5. stm:

Software Transactional Memory (STM) is a concurrency mechanism that supports composable memory transactions. It provides a way to safely manipulate shared data structures without using locks.

Here’s an example that demonstrates STM with ‘TVar‘ and the ‘atomically‘ function to safely transfer money between two bank accounts:

import Control.Monad
import Control.Concurrent
import Control.Concurrent.STM

type Account = TVar Int

main :: IO ()
main = do
  accountA <- newAccount 100
  accountB <- newAccount 200
  printAccount accountA
  printAccount accountB
  forkIO $ transfer 15 accountA accountB
  transfer 10 accountB accountA
  threadDelay 1000
  printAccount accountA
  printAccount accountB

newAccount :: Int -> IO Account
newAccount balance = atomically $ newTVar balance

printAccount :: Account -> IO ()
printAccount account = do
  balance <- atomically $ readTVar account
  putStrLn $ "Account balance: " ++ show balance

transfer :: Int -> Account -> Account -> IO ()
transfer value from to = atomically $ do
  fromBalance <- readTVar from
  when (fromBalance < value) $ retry
  writeTVar from (fromBalance - value)
  toBalance <- readTVar to
  writeTVar to (toBalance + value)

In this example, the ‘transfer‘ function uses STM to safely perform account transfers, serializing access to the account balances without using locks or blocking.

In summary, Haskell provides a rich ecosystem of libraries and abstractions for handling concurrency. Depending on your use case, you can choose the appropriate library or mechanism to deal with concurrent tasks in Haskell programs effectively.

Reading is step one. Saying it out loud is the interview. Our AI interviewer calls your phone and runs a realistic Haskell interview — then scores it.
📞 Practice Haskell — free 15 min
📕 Buy this interview preparation book: 100 Haskell questions & answers — PDF + EPUB for $5

All 100 Haskell questions · All topics