Unions in GraphQL with Mu-Haskell 0.5
by Alejandro Serrano
- •
- January 13, 2021
- •
- haskell• functional programming• functional• mu• microservices• rpc• grpc• protocol buffers• avro• graphql• openapi• swagger
- |
- 4 minutes to read.

One of the main features of Mu-Haskell is its support for different protocols. Lately, we’ve concentrated most of our efforts on GraphQL, which we’re now actively using internally at 47 Degrees. As a result, we have released several bug fixes to most packages in the Mu-Haskell ecosystem, and a major version in mu-graphql
.
Unions in GraphQL
The schema language in GraphQL supports a concept called union types. In short, being a value of a union means being a value of one of the possible types the union can take. Here’s part of GraphQL schema that defines an Author
type with associated Writing
s that can be Book
s or Article
s:
type Author {
name: String!
writings: [Writing!]!
}
union Writing = Book | Article
type Book {
title: String!
}
type Article {
title: String!
magazine: String!
}
As usual with Mu-Haskell, you can directly import this schema into a type-level representation used for checking the consistency of your server. In order to define such a server for the schema, one needs to define two elements: what is the Haskell type representing each GraphQL type (this is called the mapping), and the code that obtains the data for each field (these are called the resolvers). For a more in-depth introduction, you can check our GraphQL documentation.
The mapping works the same way for unions. Most of the time, the corresponding Haskell type is going to be a sum of the different choices. But note that this may not be the case: you can decide to only save a subset or a pointer to the information here, and fill it in in later resolvers.
data WritingMapping
= ABook BookMapping | AnArticle ArticleMapping
Resolvers for union take a special shape, though. In particular, they need to declare to Mu-Haskell which of the different choices of the union has been taken. This is done using a specialized unionChoice
constructor.
union @"Writing" (\case (ABook x) -> pure $ unionChoice @"Book" x
(AnArticle x) -> pure $ unionChoice @"Article" x)
Most of the time, this union selector is going to be quite “dumb,” like the one above. Right now, this API gives the maximum flexibility at the expense of more writing; we are exploring ways to alleviate this boilerplate to the programmer.
JSON-as-a-type
GraphQL introduces the benefits of static typing in the world of web APIs. However, sometimes an escape hatch is needed, when the information does not have a predefined shape. Since 100% of the times GraphQL is served using JSON, it was just a matter of time until somebody introduced a JSON type. This type is not part of the specification, it’s a so-called custom scalar type.
For example, we may add an optional free-form info
field to our Book
above by extending the schema as seen below. The scalar JSON
line is required by the schema language to signal that a custom scalar is being used.
scalar JSON
...
type Book {
title: String!
info: JSON
}
The resolver of that field has to return a Value
from the de facto JSON standard library in the Haskell community, namely Aeson. Here’s some code that returns an object with a score
field as that info
field:
import qualified Data.Aeson as JSON
bookInfo bookId = pure $ do
bk <- findBook bookId
pure $ JSON.object ["score" JSON..= bookScore bk]
We found the JSON scalar quite useful in those scenarios in which you cannot exactly pinpoint the shape of data, usually because the model is not yet fixed in stone.
Conclusion
With this release of Mu-Haskell, we get closer to full support of the GraphQL specification. This ties with our goal of giving a first-class experience on each different supported protocol – gRPC, OpenAPI, GraphQL – not simply downgrading to the intersection of the three.
The active development of Mu-Haskell is proudly sponsored by 47 Degrees, a Functional Programming consultancy with a focus on the Scala, Kotlin, Swift, and Haskell programming languages.