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:
We use the
Time
andThread
structures for timing and delays.The
handleRequest
function simulates handling a request by printing the request number and current time.basicRateLimiting
demonstrates simple rate limiting by processing requests with a fixed delay between each.burstyRateLimiting
implements a bursty rate limiter that allows up to 3 requests to be processed immediately before enforcing delays.Instead of channels, we use lists to represent the requests and recursive functions to process them.
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.