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 <*> regexArgIn 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:
Figure out the executable program name, convenient to provide help messages. Haskell’s standard library provides
getProgNamefor this purpose.«Run» the command line options parser. The parser would read all command line arguments as provided by the user and, either return a valid
Optionsvalue, or abort execution providing feedback to the user.Use the
Optionsvalue 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) >>= runwhere 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 = printtaking 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 sThe 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-CThe 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:
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.
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:
RegexAPIis atypealias. Not even a fulldata. It does not take runtime resources. The nameRegexAPIis 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
Capturedenotes the name of the argument, and the expected type it must have. It can be any Haskell type.QueryParamdenotes the name for an optional query element to look for and the type to expect. UsingWordensures it must be a 64-bit positive integer.Get '[JSON] adenotes 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
:<|> enumerateAPIThe 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 = Proxyand 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 regexAppWAI 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 apiregexThen, from another terminal we try
$ curl 'http://localhost:8081/match/a(a+b)*b/abbab/'
trueand 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 showYou 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 apiregexand 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 RegexAPIand 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" Emailand 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 Resultmaking 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.
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!↩︎
Don’t confuse it with
Parseras provided byParsec. 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.↩︎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.↩︎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.↩︎
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 stopis fully evaluated. If it’s true, then the whole conditional will become(n > v)wherevis the value resulting from fully evaluatingfromJust stop. If it’s false, the the whole conditional will becomefalseand never be considered again. This runtime optimization can be done, becauseisJust stopis 100% pure in value so its result is guaranteed not to change. Think about what that means for the secondwhenand how it will be runtime optimized when reached. This is not JIT compilation: it’s thunk reduction under the pure value invariant.↩︎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.↩︎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.↩︎
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
Proxyhelps in passing type (or meta-type!) information to a function, in order for the type-system to restrict it’s values and prevent «mixups».↩︎By any type I mean any type you can write in Haskell (
newtypeordata) for which you can write a parser usingParsecor evenReadinstance. 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] afor the generated server to pick the proper marshalling code.↩︎