Welcome to 12 Days of Racket Packages! The Racket Package Catalog contains 695 packages at the time of this writing (counting packages in the main distribution). That's a pretty big achievement for such a small community and a package system that's barely two years old. In hopes of helping users find great packages, we're publishing a series of 12 blog posts, once per day, starting today. Each post will detail a single package available in the catalog and describe what you might use it for and what's interesting about it. Extra attention may be paid to "Rackety" features, for instance packages providing a custom #lang. This series is in the spirit of and directly inspired by 24 Days of Hackage.
The flexpr
package (source) by Greg Hendershott is a simple package with one purpose - to work with Racket data that can be represented as either JSON or XML. Both of these formats are ubiquitous among web services. JSON is the more recent and lightweight format based on Javascript objects, and XML is the JSON-of-old, opting for a more verbose HTML-ish syntax.
Enough exposition, onwards towards examples! The first thing you'll need to do is install flexpr
:
raco pkg install flexpr
And then fire up your favorite editor or a REPL and require it:
(require flexpr)
Now suppose you have a simple hasheq
mapping symbols to values, say it represents an evil-maniacal-plan
:
(define evil-maniacal-plan
(hasheq 'setup-years 5
'cost 200000
'world-domination-percentage 85
'estimated-failure-probability 30))
Seems like a plan any world-conquering mad genius would be proud of.
As this is the age of REST-ful services on the web, further suppose our technologically conscious tyrant wishes to expose his plan to a fleet of robots through a web service. No problem, our aspiring autocrat simply creates an /evil-manical-plan
endpoint which responds with the hash using display
:
GET /evil-maniacal-plan
#hasheq((cost . 200000)
(world-domination-percentage . 85)
(estimated-failure-probability . 30)
(setup-years . 5))
Problem: some of these robots use libraries, tools, languages, etc. that limit their ability to parse Racket data (COBOL still haunts us all these years later). More robotic minions can be supported if a more universal data format is used. So our intrepid instigator abandons Racket data and opts for using the built-in json library. This library provides a notion of jsexpr
s, Racket values that can be represented as JSON, along with a write-json
function:
> (write-json evil-maniacal-plan)
{
"cost": 200000,
"world-domination-percentage": 85,
"estimated-failure-probability": 30,
"setup-years": 5
}
Much better, far more evil robots can understand JSON than Racket data. However, some older models still have problems - they can only understand XML. Racket has an xml module with xexpr
s that are similar to jsexpr
s, but for XML. Unfortunately, both jsexpr
s and xexpr
s are simply lists matching a particular form. How can our dastardly despot support both JSON and XML, and convert easily between them and Racket data? Something more flexible is needed. Enter flexpr
:
> (flexpr? evil-maniacal-plan)
#t
> (write-flexpr-json evil-maniacal-plan)
{
"cost": 200000,
"world-domination-percentage": 85,
"estimated-failure-probability": 30,
"setup-years": 5
}
> (write-flexpr-xml/content evil-maniacal-plan #:root 'evil-maniacal-plan)
<evil-maniacal-plan>
<cost>200000</cost>
<world-domination-percentage>85</world-domination-percentage>
<estimated-failure-probability>30</estimated-failure-probability>
<setup-years>5</setup-years>
</evil-maniacal-plan>
Voila! Now both JSON and XML can be easily supported, all while working with regular Racket data on the backend.
There are a few magic tricks however. Notice that #:root 'evil-maniacal-plan
keyword argument: XML documents, unlike JSON, are a list of child elements which each must have a tag name. There is no real equivalent to this root tag name requirement in JSON, so when writing XML the flexpr->xml
function gives you the option to explicitly provide a root tag name.
Further magic is revealed when an attribute with a plural name and list of values is added:
(define evil-maniacal-plan
(hasheq 'setup-years 5
'cost 200000
'world-domination-percentage 85
'estimated-failure-probability 30
'stages (list "alpha" "beta" "DOOM")))
JSON has list values, so nothing too surprising happens there:
> (write-flexpr-json evil-maniacal-plan)
{
"cost": 200000,
"world-domination-percentage": 85,
"estimated-failure-probability": 30,
"setup-years": 5,
"stages": ["alpha", "beta", "DOOM"]
}
However XML specifies that a list be a list of elements each of which must have a tag name. What does flexpr
use for the tag name? Magically, the answer is...
<evil-maniacal-plan>
<cost>200000</cost>
<world-domination-percentage>85</world-domination-percentage>
<estimated-failure-probability>30</estimated-failure-probability>
<setup-years>5</setup-years>
<stages>
<stage>alpha</stage>
<stage>beta</stage>
<stage>DOOM</stage>
</stages>
</evil-maniacal-plan>
...that 'stages (list "alpha" "beta" "DOOM")
is represented in XML as <stages><stage>alpha</stage>...</stages>
. Where did that stage
tag name come from? flexpr
has a notion of plural symbols, or Racket symbols whose English word is a plural. When flexpr->xml
encounters a key-value pair in a hash whose value is a list, it checks if the key symbol ends in "s". If it does, it truncates the "s" and uses the truncated form as the name of the child tags in the XML output.
This is a tad magic and English specific, and it doesn't account for all the other ways of pluralizing English words. Luckily flexpr
provides a hook for intercepting this behavior - current-singular-symbol
. This is a parameter that contains a procedure defining the behavior of plural symbols - it should accept a symbol and return #f
if its not plural, or return its singular form if it is plural. This allows replacement of the singularization behavior of flexpr->xml
with something more sophisticated or specific to another spoken language.
This wraps up Day 1 of 12 Days of Racket Packages! We hope you'll stay tuned for further installments. If you know of a package you'd really like to get into the spotlight, email [email protected]
Awesome you're doing this!
One question:
Does this include packages tagged
main-distribution
? If so, you might want to exclude those from the count, or least give the subtotals?