Rate Limiting in Haskell

Here’s the translation of the rate limiting example from Go to Haskell:

import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.Chan
import Control.Monad (forM_, replicateM_)
import Data.Time.Clock (UTCTime, getCurrentTime)

main :: IO ()
main = do
    -- First we'll look at basic rate limiting. Suppose
    -- we want to limit our handling of incoming requests.
    -- We'll serve these requests off a channel of the
    -- same name.
    requests <- newChan
    forM_ [1..5] $ \i -> writeChan requests i

    -- This limiter channel will receive a value
    -- every 200 milliseconds. This is the regulator in
    -- our rate limiting scheme.
    limiter <- newChan

    -- By blocking on a receive from the limiter channel
    -- before serving each request, we limit ourselves to
    -- 1 request every 200 milliseconds.
    forkIO $ forever $ do
        threadDelay 200000  -- 200 milliseconds
        writeChan limiter ()

    replicateM_ 5 $ do
        _ <- readChan limiter
        req <- readChan requests
        time <- getCurrentTime
        putStrLn $ "request " ++ show req ++ " " ++ show time

    -- We may want to allow short bursts of requests in
    -- our rate limiting scheme while preserving the
    -- overall rate limit. We can accomplish this by
    -- buffering our limiter channel. This burstyLimiter
    -- channel will allow bursts of up to 3 events.
    burstyLimiter <- newChan

    -- Fill up the channel to represent allowed bursting.
    replicateM_ 3 $ getCurrentTime >>= writeChan burstyLimiter

    -- Every 200 milliseconds we'll try to add a new
    -- value to burstyLimiter, up to its limit of 3.
    forkIO $ forever $ do
        threadDelay 200000  -- 200 milliseconds
        getCurrentTime >>= writeChan burstyLimiter

    -- Now simulate 5 more incoming requests. The first
    -- 3 of these will benefit from the burst capability
    -- of burstyLimiter.
    burstyRequests <- newChan
    forM_ [1..5] $ \i -> writeChan burstyRequests i

    replicateM_ 5 $ do
        _ <- readChan burstyLimiter
        req <- readChan burstyRequests
        time <- getCurrentTime
        putStrLn $ "request " ++ show req ++ " " ++ show time

This Haskell code implements rate limiting using channels (Chan) and threads. Here’s a breakdown of the translation:

  1. We use Control.Concurrent.Chan for channels instead of Go’s built-in channels.
  2. Go’s goroutines are replaced with Haskell’s forkIO for creating new threads.
  3. Instead of time.Tick, we use threadDelay in a loop to simulate ticking.
  4. The close function for channels is not needed in Haskell as channels are garbage collected.
  5. We use getCurrentTime from Data.Time.Clock to get the current time, similar to Go’s time.Now().

Running this program will show a similar output to the Go version, with requests being rate-limited and the bursty limiter allowing short bursts of requests.

Note that Haskell’s concurrency model is different from Go’s, and this translation aims to replicate the behavior rather than provide an idiomatic Haskell solution. In practice, you might use more Haskell-specific concurrency primitives for rate limiting.