Skip to content

Instantly share code, notes, and snippets.

@jackfirth
Last active November 4, 2015 04:41
Show Gist options
  • Save jackfirth/3d487c930b05a1213d41 to your computer and use it in GitHub Desktop.
Save jackfirth/3d487c930b05a1213d41 to your computer and use it in GitHub Desktop.

12 Days of Racket Packages - flexpr

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 jsexprs, 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 xexprs that are similar to jsexprs, but for XML. Unfortunately, both jsexprs and xexprs 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]

@jackfirth
Copy link
Author

Updated the package count to exclude main-distribution packages and main-test packages. Did not know that included over 200 packages. Wow.

@samth
Copy link

samth commented Oct 27, 2015

Sorry for the delay in responding. A few comments:

  1. I think counting the main-distribution packages seems reasonable. Most other languages don't come with a default install that includes a web framework and an IDE.
  2. I'd be a little less strong than "it wouldn't do". Maybe "we can support clients in more languages".
  3. You should use your email as the contact -- you're the author, after all.
  4. If you can format the XML on somewhat fewer lines, that might be nice.
  5. I think you should mention Greg's name up at the top, since he wrote flexpr.
  6. For the first post, maybe include some links to the pkg system docs etc.
  7. Add links to the pkgs page for flexpr, and to the github repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment