Regex as a service

Posted on 2026-02-07 by Ernesto Hernández-Novich
Tags:

After being able to answer these questions, by recursively enumerating the possibly infinite associated regular set, we turn to the issue of giving them back to the mythical «user». Those who don’t know what they want, but need it now.

That is, will finish refiniing our initial

StringLookingLikeAREGEX -> BehavesLikeAREGEX

into a fully functional (pun intended)

[Char] -> RE -> [String] -> IO ()

The first two stages were worked out as pure functions, taking advantage of Haskell’s laziness and fusion. This is far superior than interleaving I/O actions, for implementation and maintenance reasons: components are easier to reuse, their structure makes then pluggabble into other data-driven computations, and rendering presentation specifics are easily replaceable.

This post focuses on that last property.

Words are better than clicks

CLIs following the Unix philosophy have proven their power and effectiveness for over half a century, and then some. It’s easy to hate on them when you are at a lack of words to explain why. 1

A well designed CLI interface goes a long way. I’d argue that looking at a programming language’s facilities to create extensible CLI interfaces, speaks volumes about the language’s abilities. It’s not enough for a language to have a way to handle command-line arguments; all systems programming language have a way to get command line arguments. It’s about how easy it is to express what is a command, an option, an argument, and flags, yet, more importantly, make it easy to extend or refactor.

For Haskell, optparse-applicative is the de facto standard. The programmer describes how to process the arguments that will be provided on the command line, and the library automatically derives an efficient parser not only able to handle all combinations, but also to provide context-appropriate feedback when the user makes a mistake. Processing descriptions are monoidal for easier composition. The derived parser being applicative helps turn options into internal type-safe representations.

Design, model, declare…

Our CLI program has to either enumerate or attempt to match, and it always needs a regex to work with. Something like

$ enumeregex enumerate ... <string-used-as-regex>

or

$ enumeregex match ... <string--used-as-regex>

In optparse-applicative parlance, both enumerate and match are sub-commands, and <string-used-as-regex> would be an argument. Since enumerating and matching have different behaviors, they take different, and possibly optional flags to modify each one’s behavior.

Properly modeling the above as Haskell types, we have

data Options = Options { cmd   :: Command 
                       , regex :: String
                       }
                       deriving (Show)

data Command = Enumerate { beginAt :: Maybe Word
                         , endAt   :: Maybe Word
                         , numbers :: Bool
                         }
             | Match     { word    :: String }
             deriving (Show,Eq)

Notice how the «top-level» Options is concerned with what a particular invocation has to do, while Command is concerned with specific details for the command: a Match requires a String to try; Enumerate might work over a specific range, or might want the enumeration index added to each word. This makes it easy to extend the command set, the variations for existing commands, and the arguments common to all commands. That way, each Options value would encapsulate and be immediately suitable as the «environment», «context», or however you want to call the expected running behavior for a particular invocation. As long as we can parse it from the command line arguments.

We can use Parser 2 from optparse-applicative to express what to capture and any transformations needed. The library will generate the actual parser.

commandLineOptions :: Parser Options
commandLineOptions = Options <$> commandArg <*> regexArg

In order to parse a valid Options (the type) we can use Options (the constructor) to sequence a parser for commands, followed by a parser for the argument. As long as their respective parsers return corresponding Command and String values, we’ll end up with a valid Options value.

The String argument that shall be used as a regex, can be parsed as follows

regexLabel = "<REGEX>"

regexArg :: Parser String
regexArg = argument str ( metavar regexLabel
                          <> help "Regular expression to use"
                        )

Combinator argument indicates it must always be present, and str is a combinator that will parse anything as long as it can be converted to a String. Just what we need. The attributes for a parser are built as a monoid: in this case metavar and help are combinators to, unsurprisingly, assist in the automatic generation of feedback to users typing an invalid command. 3

Parsing the Command options is more interesting yet equally easy. There are multiple commands, each one taking specific contextual options. Combinator subparser helps declaring this, indicating nested mutually exclusive, parsers each one having independent processing flow, and its own local attributes.

commandArg :: Parser Command
commandArg = subparser (
               metavar "<COMMAND>"
               <>
               help    ( "What to do with " <> regexLabel )
               <>
               command "enumerate" 
                       (info enumerate0 ( progDesc $ "Enumerate words from " <> regexLabel ))
               <>
               command "match"
                       (info match0     ( progDesc $ "Match using " <> regexLabel ))
             )

Notice how the subparser’s attributes are the already described metavar and help, but also nested parsers for enumerate and match cases. I’ve specified them in the same order the data-type has them, but that is not mandatory: attributes are monoidal so they will all be aggregated into a single value, and the parser will use produce the corresponding returning value appropriately. After all, it’s just a Command value, but we need to produce them.

Combinator command is accompanied by the expected literal string. But each command has to produce a different value: one for Enumerate, and another for Match. That’s the purpose of info: use the specific subparser as declared locally

For "enumerate", we need to parse the possibly optional flags for bounds

  where
    enumerate0 = Enumerate <$>
      optional (option auto ( metavar "<FROM>"
                              <>
                              long "from"
                              <>
                              short 'f'
                              <>
                              help "Enumerate from this word on (>= 0)" ))
      <*>
      optional (option auto ( metavar "<TO>"
                              <>
                              long "to"
                              <>
                              short 't'
                              <>
                              help "Enumerate until this word (>= 0)" ))

where long and short define what string or letter to use for the flag’s tag. Looking into the Enumerate constructor, there are two Maybe Int to produce: combinator option will provide the Maybe a wrapping, while combinator auto will read the String argument and try to parse it into the needed target type, thus completing our Maybe Int requirement.

Finally, our design for Command includes the option to list each word with its enumeration index. This flag is either present or not, a behavior specified with combinator switch, unsurprisingly signaling its result with a Bool value.

      <*>
      switch ( long "numbers"
               <> short 'n'
               <> help "Show word numbers" ) 

The "match" command always needs a string option providing the word to try and match with the regex. This is exactly what the strOption combinator is for

    match0 = Match <$> strOption ( metavar "<STRING>"
                                   <>
                                   long "word"
                                   <> help ("Does " <> regexLabel <> " match <STRING>?")
                                 )

As with any parser, we need to «run it». The main program for our CLI needs to:

  1. Figure out the executable program name, convenient to provide help messages. Haskell’s standard library provides getProgName for this purpose.

  2. «Run» the command line options parser. The parser would read all command line arguments as provided by the user and, either return a valid Options value, or abort execution providing feedback to the user.

  3. Use the Options value to enumerate or match.

A simple top-level function implementing the above step by step starts with

runner :: IO ()
runner = do
  pn <- getProgName
  execParser (opts pn) >>= run

where execParser runs the locally declared parser opts, and run takes a valid Options value to perform the specified command.

The main parser opts relies on the already described commandLineOPtions parser we wrote, alongside a default helper parser provided by the library that properly reacts to invalid command line arguments. The rest are documentation attributes:

  where
    opts name = info ( commandLineOptions <**> helper )
                     ( fullDesc
                     <> progDesc description
                     <> header (name ++ " -- enumerate or match with a regular expression")
                     )

    description =  "Given a regular expression, enumerate words "
                <> "in the corresponding regular set, or try to "
                <> "match a specific word."

This default parser is actually very clever when it comes to handling errors and providing feedback:

$ stack exec enumeregex -- -h
enumeregex -- enumerate or match with a regular expression

Usage: enumeregex <COMMAND> <REGEX>

Given a regular expression, enumerate words in the corresponding regular set,
or try to match a specific word.

Available options:
  <COMMAND>                What to do with <REGEX>
  <REGEX>                  Regular expression to use
  -h,--help                Show this help text

Available commands:
  enumerate                Enumerate words from <REGEX>
  match                    Match using <REGEX>
$ stack exec enumeregex -- enumerate
Missing: <REGEX>

Usage: enumeregex <COMMAND> <REGEX>

Given a regular expression, enumerate words in the corresponding regular set,
or try to match a specific word.
$ stack exec enumeregex -- match
Missing: --word <STRING>

Usage: enumeregex match --word <STRING>

  Match using <REGEX>

The conveniently formatted help messages are built automatically out of all the attibute combinators. Note how match requires the --word flag. You can even ask for contextual help for any of the commands

$ stack exec enumeregex -- -h enumerate
Usage: enumeregex enumerate [-f|--from <FROM>] [-t|--to <TO>] [-n|--numbers]

Enumerate words from <REGEX>

Available options:
  -f,--from <FROM>         Enumerate from this word on (>= 0)
  -t,--to <TO>             Enumerate until this word (>= 0)
  -n,--numbers             Show word numbers

And, as you can imagine, you’ll get appropriate messages if you mispell options, or provide values that aren’t numbers. Pretty neat considering we only declared what we expected from each parser.

…and execute

After the top level parser is able to produce a valid Options value, we are sure we have a String that should be used as regex, and all additional flags needed to do what the user requested on invocation. We need to run:

We should use the pure parseRegex to try and parse the argument string. Recall this parser returns an Either: a parsing error or an RE value. Hence the succint and idomatic

run :: Options -> IO ()
run options = either 
                badRegex 
                (results options)
                (parseRegex (regex options))

badRegex :: TP.ParseError -> IO ()
badRegex = print

taking advantage of Data.Either.either: try and apply parseRegex on the regex argument taken from the options. If parsing fails, pass the parsing error to badRegex. If parsing succeeds, pass the RE to

results :: Options -> RE -> IO ()
results options re = case cmd options of
  Match this      -> if matches re this
                        then putStrLn "Match!"
                        else putStrLn "No match!"

to figure out what to do depending on the particular Command. To run Match, just apply the pure matches to the valid RE and argument strings. Running it would result in something like

$ stack exec enumeregex -- match --word abba 'a(a+b)*a'
Match!
$ stack exec enumeregex -- match --word abba 'a(a+b)*b'
No match!

or this

$ stack exec enumeregex -- match --word abba 'a(a+b*a'
(line 1, column 8):
unexpected end of input
expecting "+" or ")"

if you pass an invalid regular expression.

Running Enumerate requires a bit more work, because there could be optional arguments mf and mt, as well as maybe a switch to print the enumeration index.

We can use zip over the pure enumerate to lazily build the indexed enumeration list

  Enumerate mf mt n -> forM_ results' (showWord mf mt n)

  where
    results' :: [(Word,String)]
    results' = zip [0..] (enumerate re)

Now, we need to output the enumerated words, possibly preceded by their index. This requires performing an IO () monadic action for every element of the possibly infinite list 4, hence the forM_.

    showWord :: Maybe Word -> Maybe Word -> Bool -> (Word,String) -> IO ()
    showWord start stop nums (n,w) = do
      when (isJust stop  && n >  fromJust stop) exitSuccess
      when (isNothing start ||
            (isJust start && n >= fromJust start)) $ do
        when nums (putStr $ show n ++ ": ")
        wordOrLambda w

    wordOrLambda :: String -> IO ()
    wordOrLambda s
      | null s    = putStrLn "λ"
      | otherwise = putStrLn s

The use of when (or guard, not needed for this fragment) is an idiomatic way to perform monadic actions conditionally.

The first when checks if there’s a specified final index to print. If there is and the current index is greater than it, force the program to succesfully terminate. That is, early termination.

The second when checks if there isn’t a specified initial index to print, or if there is and the current index is greater than it. In either case, conditionally output the enumeration index, and always output the enumerated word. That is, start printing as soon as needed.

If the enumeration list is infinite, enumeration will terminate as long as a final index is provided, otherwise run until interrupted. If the enumeration list is finite, the above will terminate with the list.

Is worth mentioning that due to Haskell’s laziness, all the isJust, isNothing, and fromJust above will be evaluated exactly once. 5

We can use it like so

$ stack exec enumeregex -- enumerate -t 3 'a(a+b)*b'
ab
aab
abb
aaab
$ stack exec enumeregex -- enumerate -n -t 3 'a(a+b)*b'
0: ab
1: aab
2: abb
3: aaab
$ stack exec enumeregex -- enumerate -n -f 2 -t 5 'a(a+b)*b'
2: abb
3: aaab
4: aabb
5: abab
$ stack exec enumeregex -- enumerate -n -f 42 'a(a+b)*b'
42: aababbb
43: aabbaab
44: aabbabb
45: aabbbab
46: aabbbbb
48: abaaabb
...
Ctrl-C

The astute (or confused reader) should have noted the code doesn’t need to know or care for the length of the list, nor needs a while (true) to move over the list. The list is the control structure. And given how we’re using it, the list doesn’t even exist: elements are produced one at a time from the pure enumerate to feed the monadic forM_, and once showWord outputs the word, the element will be garbage collected. That is to say, it runs in constant space regardless of how many words you need to enumerate.

Our CLI is now complete, and you can use it as part of shell scripts and Unix pipelines, because it uses standard CLI arguments, spits out results to standard output, and errors to standard error.

That’s not a service!

The above seems like a lot of work for a CLI interface. If you’ve ever written something like this with the precarious getopt(3) as provided by C, or its equivalent for shell scripting, Perl, Ruby, Python, or Go, you know what a lot of work means, and you’ll probably scratching your head now. Good.

But we don’t have a service, in the sense The Cool Kids® want services nowadays, have we? We’re supposed to have an API we can query using curl or worse HTTP clients, and get back JSON payloads, and whatnot.

Fair enough.

Design, model, declare…

The shortest path to an HTTP-based API in Haskell is Servant. This is not going to be a tutorial, just a tour de force that will hopelly open your eyes towards good engineering, or send you in a path of resentment and despair over time wasted.

Say we want our API to have two endpoints:

  1. match, where both the regex to use, and the word to try and match, should be part of the URL path

     /match/<regex>/<word>

returning a boolean JSON value.

  1. enumerate, where the regex to use should be part of the URL path, and the optional starting and ending words are passed as query arguments

     /enumerate/<regex>?from=23&to=42

returning a JSON list holding the enumerated words.

Too keep things so simple it will hurt, we’ll only use HTTP verb GET, and ignore the fact that enumerate would definitely benefit from chunking.

Servant provides combinators so you can model any API as a Haskell data type. The library then leverages Haskell’s type-level computation to generate type-safe fully-functional scaffolding with you, the programmer, needing to fill some blanks. To model our API design, we write

type RegexAPI =    "match" 
                   :> Capture "regex" String :> Capture "word" String
                   :> Get '[JSON] Bool
              :<|> "enumerate"
                   :> Capture "regex" String
                   :> QueryParam "from" Word :> QueryParam "to" Word
                   :> Get '[JSON] [String]

Look back and forth between design and model. Again. In case it’s not clear or you’re in disbelief:

  • RegexAPI is a type alias. Not even a full data. It does not take runtime resources. The name RegexAPI is used so we don’t have to write the type combinators over and over.

  • Those :> and :<|> are type operators. 6 The former is usually roughly equivalent to URL /, the latter is used to denote alternative API endpoints.

  • Each Capture denotes the name of the argument, and the expected type it must have. It can be any Haskell type.

  • QueryParam denotes the name for an optional query element to look for and the type to expect. Using Word ensures it must be a 64-bit positive integer.

  • Get '[JSON] a denotes the HTTP verb to accept for this endpoint, the MIME application type to use for respones, and the response’s content type 7.

Let’s write a server for this API type. Servant’s Server monad takes this type alias to figure out everything needed to parse and process HTTP requests, an produce HTTP responses, provided we «fill in the blank» with one function for each endpoint.

regexServer :: Server RegexAPI
regexServer =  matchAPI
          :<|> enumerateAPI

The blanks, one for each endpoints, are «handler functions». Functions are combined with the value-level :<|> operator matching the type-level :<|> operator. You do not need to remember the order in which to put them, because the compiler will tell you if your write them out of order. You do not need to worry about forgetting one, because the compiler will demand you do it. No more runtime errors on account of poor attention to detail. Each function runs in the Handler monad: suffice to say this monad has a lot of helper functions for you to access the actual HTTP request and build your response. And a lot more.

Since both endpoints are going to receive a String that should be a valid regex, let’s start by writing a simple higher-order monadic helper function withRegex that will try and parse it: if it is a valid RE, use it right away; if there’s a parse error, produce a custom HTTP 404 response. You’ve seen either used before, haven’t you?

  where
    withRegex :: String -> (RE -> a) -> Handler a
    withRegex regex f = either
                            (throwError . custom404 regex)
                            (pure . f)
                            (parseRegex regex)

    custom404 :: String -> ParseError -> ServerError
    custom404 regex msg = err404 { errBody =  "Invalid Regex: '"
                                           <> fromString regex
                                           <> "'\n"
                                           <> fromString (show msg)
                                           <> "\n"
                                 }

Handler’s monadic behavior is such that throwError results in the proper failure HTTP response being returned by Servant, whilst custom404 takes advantage of err404 combinator to build the payload. The fromString conversions are there to use the high-performance ByteString type: there are better ways to do this, but that’s not the point. Notice how withRegex is there to have a specific result polymorphic f to «do something» with the valid RE after a parse.

The match endpoint captures two Strings: the would be regex, and the word to try and match, hence:

    matchAPI :: String -> String -> Handler Bool
    matchAPI regex word = withRegex regex (`matches` word)

Recall withRegex regex will do the parsing, and then matches will produce True or False. The Handler monad will take care of serializing the Haskell Bool into a JSON boolean.

As for enumerate endpoint, it captures the String would be regex, and could receive a couple of optional query parameters. The Handler monad will grab them if they’re present, and return them as Maybe values, hence:

    enumerateAPI :: String -> Maybe Word -> Maybe Word -> Handler [String]
    enumerateAPI regex mf mt = withRegex regex (section mf mt . enumerate)
      where
        section :: Maybe Word -> Maybe Word -> [a] -> [a]
        section Nothing    Nothing    = id
        section Nothing    (Just max) = take (fromIntegral max)
        section (Just min) Nothing    = drop (fromIntegral min)
        section (Just min) (Just max) = take (fromIntegral max - fromIntegral min + 1)
                                      . drop (fromIntegral min)

Recall withRegex regex will do the parsing, and then the locally defined section will pick the words produced by enumerate, only if they are in the range as specified by the optional query parameters. I made a point of writing this in a pure list-processing form. The atentive reader should note that section is a function that returns a function, which makes it even more elegant. The Handler monad will take care of lazily consuming the [String] while serializing it to JSON. If you’re thinking «so the list doesn’t even exist because elements are produced one by one», you’ve got it!

Done writing the handlers.

…and execute

The final step is having an HTTP server whence to operate our API. Servant’s «server part», provides a function serve. It receives the encoded API type information 8

regexAPI :: Proxy RegexAPI
regexAPI = Proxy

and the handler definitions written in regexServer above

regexApp :: Application
regexApp = serve regexAPI regexServer

to generate a fully-functional WAI (Web Application Interface) Application. This Application can be served straight away by WARP on port 8081 like so

main :: IO ()
main = do
  withStdoutLogger $ \logger -> do
    let settings = setPort 8081 $ setLogger logger defaultSettings
    runSettings settings regexApp

WAI has the concept of «middleware», such that any application can be enhanced by stacking ready-made functionality you don’t need to write in full. You could use that to inject headers, add on-the-fly compression, HTTP Basic Authentication, virtual hosting, or logging, among other things. For this example, I’ll add simple Apache-like logging to standard out.

WARP is a fully functional HTTP server written in pure Haskell, that performs as good or better than nginx. It only runs Applications: runSettings gets whatever configuration you need, and the application.

After compiling our 63 lines of Haskell code for the API implementation, we run it like this

$ stack exec apiregex

Then, from another terminal we try

$ curl 'http://localhost:8081/match/a(a+b)*b/abbab/'
true

and the first terminal will show

$ stack exec apiregex
127.0.0.1 - - [07/Feb/2026:15:23:31 -0800] "GET /match/a(a+b)*b/abbab/ HTTP/1.1" 200 167 "" "curl/8.14.1"

and in case you’re wondering

$ curl -i 'http://localhost:8081/match/a(a+b)*b/abba/'
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Sat, 07 Feb 2026 23:24:50 GMT
Server: Warp/3.4.8
Content-Type: application/json;charset=utf-8

false

Using the other endpoint with query arguments works as expected

$ curl -i 'http://localhost:8081/enumerate/a(a+b)*b?from=5&to=10'
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Sat, 07 Feb 2026 23:27:00 GMT
Server: Warp/3.4.8
Content-Type: application/json;charset=utf-8

["abab","abbb","aaaab","aaabb","aabab","aabbb"]

So, there’s the API you wanted, bub.

Are you kidding?

Wait, there’s more!

You show the above to your boss after maybe 15 minutes of work to write those 63 lines. He stares at you, a mix of angry disbelief and contempt at your technical audacity disregarding all that is «sacred and corporate». Their reaction is «WTF? Sure… but WHY didn’t you implement plain text as a response type?».

This is what you do, ideally in front of them, while using vi for double damage on account of rolling a natural 20 for suprise:

type RegexAPI =    "match" 
                   :> Capture "regex" String :> Capture "word" String
                   :> Get '[JSON, PlainText] Bool
              :<|> "enumerate"
                   :> Capture "regex" String
                   :> QueryParam "from" Word :> QueryParam "to" Word
                   :> Get '[JSON, PlainText] [String]

instance MimeRender PlainText Bool where
  mimeRender _ = fromString . show

instance Show a => MimeRender PlainText [a] where
  mimeRender _ = fromString . unlines . map show

You might need to use your finger to tell the boss «see how I just added PlainText there and there, and told the compiler how to render our custom types?». Then

$ stack build
$ stack exec apiregex

and test

$ curl -i 'http://localhost:8081/enumerate/a(a+b)*b?from=5&to=10'
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Sat, 07 Feb 2026 23:48:51 GMT
Server: Warp/3.4.8
Content-Type: application/json;charset=utf-8

["abab","abbb","aaaab","aaabb","aabab","aabbb"]
$ curl -i -H 'Accept: text/plain'  'http://localhost:8081/enumerate/a(a+b)*b?from=5&to=10'
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Sat, 07 Feb 2026 23:50:17 GMT
Server: Warp/3.4.8
Content-Type: text/plain;charset=utf-8

"abab"
"abbb"
"aaaab"
"aaabb"
"aabab"
"aabbb"

The next complain might be related to «Sure, but now we need to write the client for it!». It’s a good thing that Servant’s «client part», allows

getMatch :: String -> String -> ClientM Bool
getEnumerate :: String -> Maybe Word -> Maybe Word -> ClientM [String]
getMatch :<|> getEnumerate = client RegexAPI

and then just use independent and automatically generated functions getMatch and getEnumerate in the ClientM monad, via runcClientM.

What do you mean your programming language doesn’t allow you to generate two functions as part of a recursive structure, and split them using pattern matching?

True genericity

Thanks to Haskell’s and GHC’s pervasive support for true generic programming, you can make any type fully parsable as an URL path, URL query argument, or payload type 9. I did not take advantage of those features in this example, but you should certainly imagine

data Email = Email { ... }
data Result = Ok | NotOk

parseEmail :: String -> Either ParseError Email

type EmailAPI = "email" :> "create" :> Capture "mbox" Email

and if you provide something that does not parse like type Email, the API URL will immediately produce a 404. But if it does, you handler will be

createEndpoint :: Email -> Handler Result

making your API 100% type-safe, based in actual parsing, not «validating and wishing for the best String as Poor Man’s Data Type®». And if you’re paying attention, this means way less tests are needed, because you already know what’s going to happen with invalid inputs.

An API is a lot of work. But it should be for the language and compiler, not the programmer. For that, you need a modern type system, not just «the best the 1990’s can offer».

Anything else would be uncivilized.


  1. I’m glad I paid attention to my Operating Systems’ professor back in 1988: «the world will run on something that works like Unix, don’t waste your time on the point-and-click sillyness that’s coming». Thank you, POC!↩︎

  2. Don’t confuse it with Parser as provided by Parsec. It’s always a good idea to work out CLI parsing in a separate module, in the same way I wrote the regex parser in a separate module.↩︎

  3. Since attributes are monoidal, you can also write using the idiomatic

    regexArg :: Parser String
    regexArg = argument str
             $ mconcat [ metavar regexLabel
                       , help "Regular expression to use" 
                       ]

    which could be nicer for longer lists of attributes. Once you understand Monoid, which style is a matter of personal preference.↩︎

  4. This is the only place in the whole program, where we must interleave pure computations with IO actions. It’s isolated, easy to identify, and simpler to reason about, than if we wrote «Haskell as if it were C» for the whole thing. Get in the habit of isolating these kind of fragments.↩︎

  5. The boolean expressions will be reduced by Haskell’s runtime to minimal work once the program runs and evaluates them for the first time. This is unusual for non-Haskellers or programmers on eager languages. Consider the first when. The first time it is reached, isJust stop is fully evaluated. If it’s true, then the whole conditional will become (n > v) where v is the value resulting from fully evaluating fromJust stop. If it’s false, the the whole conditional will become false and never be considered again. This runtime optimization can be done, because isJust stop is 100% pure in value so its result is guaranteed not to change. Think about what that means for the second when and how it will be runtime optimized when reached. This is not JIT compilation: it’s thunk reduction under the pure value invariant.↩︎

  6. In Haskell, you can compute at the value level ( applying functions to values, as in parseRegex str, duh!) but you can also compute at the type level: type operators can combine type values to create more complex type values. And you can come up with your own type operators and your own type computations. This is used to express type constraints and derivations that are simply not possible in other type systems, allowing for higher order, type safe, statically verifiable, generic programming. Read Thinking With Types (Maguire) for a hands-on practical approach.↩︎

  7. This example uses JSON only, but any endpoint could answer with any combination of JSON, plain text, HTML, or any other application type you see fit. Servant’s generated scaffolding will react appropriately to the Accept headers provided by the client, and automatically format the results accordingly.↩︎

  8. This is a zero-cost value. It does not exist at runtime, and it’s only useful at compile time to generate Haskell code. The Proxy helps in passing type (or meta-type!) information to a function, in order for the type-system to restrict it’s values and prevent «mixups».↩︎

  9. By any type I mean any type you can write in Haskell (newtype or data) for which you can write a parser using Parsec or even Read instance. Nested or recursive, doesn’t matter. Haskell’s compiler is able to automatically generate JSON marshalling and unmarshalling code for you. You can write HTML/XML entity marshalling and unmarshalling code, and take advantage of existing strongly-typed HTML/XML parsers and generators, and then just writing

    ... :> Get '[JSON, PlainText, HTML, XML] a

    for the generated server to pick the proper marshalling code.↩︎