Let’s say I want to do something simple but slightly beyond the scope of a traditional toy demonstration:
* Read some environment variables and a local file
* Start a monitoring thread that consumes from a channel or something similar, then every X s or X events writes to a local temp file and then sends a request batching some metrics to an external system
* Configure and start an http server
* Said server has a handler that 0. Starts a timer 1. loads, then increments an atomic “num requests served until now” variable 2. uses synchronization to lock on a list or ring buffer containing the last 5 requests’ user-agent headers 2.5 copies the current last 5 values, replaces oldest one with the one from the handles request, unlocks 3. generates a json response containing like “num_so_far: x, last5agent: [..], “some_env_var”:..” 3.5 stops the timer 4. write request user agent and time interval to monitoring thread’s channel 5. write response and end handling
* server’s gotta be able to do concurrency > 1 with parallelism
* On sigterm set the server to a state that rejects new requests, waits for existing requests to complete, then flushes the monitoring channel
I’d consider this a trial run of some of the most basic patterns commonly used by networked software: init io, immutable shared state, atomic mutable shared state, synchronization locked shared state, http ingress and egress, serialization, concurrency, parallelizarion, background threads, os signals, nontrivial cleanup. In Go, Java, or C++ I could write this with my eyes closed. How easy is it in Haskell or Lisp?
If you know of any demos or repos that do something like this - not a pure toy or barebones demo, but not a huge task all in all- in either I’d be interested in seeing what it looks like.
There are bunches of web frameworks and various support libraries for both Haskell and for Common Lisp. They'll range from simple use cases to more complete and/or opinionated in style, depending what your needs are. For Haskell examples, Servant is used for web APIs, where Yesod is a larger all-around framework.
I'll cover the Haskell side because I'm more familiar with its library ecosystem:
> Read some environment variables and a local file
import System.Environment (getEnv)
import System.FilePath ((</>))
main = do
dir <- getEnv "WHATEVER_DIR"
data <- readFile (dir </> "foo.txt")
putStrLn ("Had " ++ show (length (lines data)) ++ " lines")
> Start a monitoring thread [...]
import Control.Concurrent
import Control.Concurrent.Chan
import Network.HTTP
-- also more stuff...
monitoringThread :: Chan String -> IO ()
monitoringThread chan = do
file <- openFile "log.txt" AppendMode
forever $ do -- no $ needed if you pass -XBlockArguments [0, 1]
batch <- replicateM 5 (readChan chan)
let chunk = unlines batch
hPutStr file chunk
simpleHTTP (postRequestWithBody "" "text/plain" chunk)
main :: IO ()
main = do
logChan <- newChan
void (forkIO (monitoringThread logChan))
-- ...
forever $ do
threadDelay (1000 * 1000) -- 1M usec
writeChan logChan "Hello, world!"
> Configure and start an http server
import Network.Wai
import Network.Wai.Handler.Warp
main = run 8000 $ \req respond ->
respond (responseLBS status200 [] "Hello, world!")
> Said server has [...]
Yeah, this is long. If you're just getting the current time with the timer, that's here[2]; synchronize across threads with MVars[3] or STM[4]; JSON is in aeson[5], which should feel broadly familiar if you know Rust's serde.
> server’s gotta be able to do concurrency > 1 with parallelism
Yep, GHC Haskell has _excellent_ concurrency support on top of a parallel runtime.
> On sigterm set the server to a state that rejects new requests, waits for existing requests to complete, then flushes the monitoring channel
I haven't personally tried this, but this[6] function sounds like... exactly this, actually, so I think its example should suffice?
On two separate notes:
- Common Lisp and Python 3 are a _lot_ closer than Common Lisp and Haskell, or even Python 3 and JavaScript; the Python 3 object model is very close to Common Lisp's, and Common Lisp is not particularly pure (setf isn't unidiomatic by a longshot), and supports a very non-functional style of programming (it has gotos!).
- "Haskell is worse at IO than other high-level languages" isn't particularly true. What _is_ true is that Haskell has the same "function coloring problem" as JavaScript (Haskell has the IO monad, JavaScript has the Promise monad); Haskell also has a "uses funny academic words" problem (well, debatably a problem...) which I think confuses the issue.
[1]: Haskell has a spec, one "main" implementation (GHC), little spec-committee activity, and a respect for that implementation not superseding the spec; many improvements become language extensions (-X flags or {-# LANGUAGE #-} pragmas), so when you invoke GHC you're getting a by-the-spec implementation by default.
* Read some environment variables and a local file
* Start a monitoring thread that consumes from a channel or something similar, then every X s or X events writes to a local temp file and then sends a request batching some metrics to an external system
* Configure and start an http server
* Said server has a handler that 0. Starts a timer 1. loads, then increments an atomic “num requests served until now” variable 2. uses synchronization to lock on a list or ring buffer containing the last 5 requests’ user-agent headers 2.5 copies the current last 5 values, replaces oldest one with the one from the handles request, unlocks 3. generates a json response containing like “num_so_far: x, last5agent: [..], “some_env_var”:..” 3.5 stops the timer 4. write request user agent and time interval to monitoring thread’s channel 5. write response and end handling
* server’s gotta be able to do concurrency > 1 with parallelism
* On sigterm set the server to a state that rejects new requests, waits for existing requests to complete, then flushes the monitoring channel
I’d consider this a trial run of some of the most basic patterns commonly used by networked software: init io, immutable shared state, atomic mutable shared state, synchronization locked shared state, http ingress and egress, serialization, concurrency, parallelizarion, background threads, os signals, nontrivial cleanup. In Go, Java, or C++ I could write this with my eyes closed. How easy is it in Haskell or Lisp?
If you know of any demos or repos that do something like this - not a pure toy or barebones demo, but not a huge task all in all- in either I’d be interested in seeing what it looks like.