Rate Limiting in PureScript

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

module Main where

import Prelude

import Effect (Effect)
import Effect.Console (log)
import Effect.Now (nowDateTime)
import Effect.Aff (Aff, launchAff_, delay)
import Effect.Aff.AVar (AVar, new, put, take)
import Data.DateTime (DateTime)
import Data.Time.Duration (Milliseconds(..))
import Data.Traversable (for_)
import Control.Parallel (parallel, sequential)

main :: Effect Unit
main = launchAff_ 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 an AVar of the same name.
  requests <- new
  for_ (1 .. 5) \i -> put i requests

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

  -- By blocking on a take from the limiter before serving each request,
  -- we limit ourselves to 1 request every 200 milliseconds.
  let processRequests = do
        req <- take requests
        _ <- take limiter
        now <- nowDateTime
        log $ "request " <> show req <> " " <> show now

  -- Start the limiter
  let runLimiter = do
        delay (Milliseconds 200.0)
        put unit limiter
        runLimiter

  -- Run the limiter and process requests in parallel
  sequential $ parallel (processRequests *> processRequests *> processRequests *> processRequests *> processRequests)
            *> parallel runLimiter

  -- 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
  -- using multiple slots in our limiter. This burstyLimiter
  -- will allow bursts of up to 3 events.
  burstyLimiter <- new
  
  -- Fill up the AVar to represent allowed bursting.
  for_ (1 .. 3) \_ -> put unit burstyLimiter

  -- Every 200 milliseconds we'll try to add a new
  -- value to burstyLimiter, up to its limit of 3.
  let runBurstyLimiter = do
        delay (Milliseconds 200.0)
        put unit burstyLimiter
        runBurstyLimiter

  -- Now simulate 5 more incoming requests. The first
  -- 3 of these will benefit from the burst capability
  -- of burstyLimiter.
  burstyRequests <- new
  for_ (1 .. 5) \i -> put i burstyRequests

  let processBurstyRequests = do
        req <- take burstyRequests
        _ <- take burstyLimiter
        now <- nowDateTime
        log $ "request " <> show req <> " " <> show now

  -- Run the bursty limiter and process bursty requests in parallel
  sequential $ parallel (processBurstyRequests *> processBurstyRequests *> processBurstyRequests *> processBurstyRequests *> processBurstyRequests)
            *> parallel runBurstyLimiter

This PureScript code implements rate limiting using the Effect.Aff module for asynchronous effects and Effect.Aff.AVar for synchronization. Here’s a breakdown of the translation:

  1. We use AVar instead of channels for communication between concurrent processes.
  2. The time.Tick function is replaced with a recursive function that uses delay to simulate ticking.
  3. Goroutines are replaced with parallel computations using the parallel and sequential functions from Control.Parallel.
  4. The fmt.Println calls are replaced with log from Effect.Console.
  5. We use nowDateTime from Effect.Now to get the current time.

The structure and logic of the rate limiting remain the same:

  • We first demonstrate basic rate limiting, processing one request every 200 milliseconds.
  • Then we show a bursty rate limiter that allows up to 3 requests to be processed immediately, while maintaining the overall rate limit.

To run this program, you would need to compile it with the PureScript compiler and then execute it. The output would be similar to the Go version, showing the timing of each request being processed.

This implementation showcases PureScript’s approach to concurrency and asynchronous programming, utilizing its strong type system and pure functional paradigm while achieving the same rate limiting behavior as the original Go code.