Rate Limiting in Standard ML

Here’s the translation of the Go rate limiting example to Standard ML, with explanations adapted for the language:

(* Rate limiting is an important mechanism for controlling resource
   utilization and maintaining quality of service. Standard ML can
   support rate limiting using threads and timers. *)

structure RateLimiting = struct
  open Time
  open Thread

  (* Simulate a request handler *)
  fun handleRequest req =
    print ("request " ^ Int.toString req ^ " " ^ 
           toString (now()) ^ "\n")

  (* Basic rate limiting *)
  fun basicRateLimiting () =
    let
      val requests = List.tabulate (5, fn i => i + 1)
      fun processRequests [] = ()
        | processRequests (req::rest) =
            (delay (fromMilliseconds 200);
             handleRequest req;
             processRequests rest)
    in
      processRequests requests
    end

  (* Bursty rate limiting *)
  fun burstyRateLimiting () =
    let
      val requests = List.tabulate (5, fn i => i + 1)
      val burstyLimiter = ref 3  (* Allow bursts of up to 3 events *)
      
      fun processRequests [] = ()
        | processRequests (req::rest) =
            if !burstyLimiter > 0 then
              (decr burstyLimiter;
               handleRequest req;
               processRequests rest)
            else
              (delay (fromMilliseconds 200);
               burstyLimiter := 3;  (* Refill the burst capacity *)
               handleRequest req;
               processRequests rest)
    in
      processRequests requests
    end

  fun main () =
    (print "Basic Rate Limiting:\n";
     basicRateLimiting ();
     print "\nBursty Rate Limiting:\n";
     burstyRateLimiting ())
end

(* Run the example *)
val _ = RateLimiting.main ()

In this Standard ML implementation:

  1. We use the Time and Thread structures for timing and delays.

  2. The handleRequest function simulates handling a request by printing the request number and current time.

  3. basicRateLimiting demonstrates simple rate limiting by processing requests with a fixed delay between each.

  4. burstyRateLimiting implements a bursty rate limiter that allows up to 3 requests to be processed immediately before enforcing delays.

  5. Instead of channels, we use lists to represent the requests and recursive functions to process them.

  6. The burstyLimiter is implemented as a reference to an integer, which is decremented for each burst request and refilled after a delay.

Running this program will show the first batch of requests handled once every ~200 milliseconds as desired:

Basic Rate Limiting:
request 1 2023-05-20 10:15:30.123
request 2 2023-05-20 10:15:30.323
request 3 2023-05-20 10:15:30.523
request 4 2023-05-20 10:15:30.723
request 5 2023-05-20 10:15:30.923

Bursty Rate Limiting:
request 1 2023-05-20 10:15:31.123
request 2 2023-05-20 10:15:31.123
request 3 2023-05-20 10:15:31.123
request 4 2023-05-20 10:15:31.323
request 5 2023-05-20 10:15:31.523

For the second batch of requests (bursty limiting), we serve the first 3 immediately because of the burstable rate limiting, then serve the remaining 2 with ~200ms delays each.

Note that Standard ML doesn’t have built-in concurrency primitives like goroutines, so this implementation uses a simpler, sequential approach. For more complex scenarios, you might need to use additional libraries or language extensions that provide better concurrency support.