Graphqelm: optional arguments in a language without optional arguments

Mario Martinez
5 min readAug 5, 2018

--

Edit 10th September 2018

This post was written for Elm 0.18 and with the release of Elm 0.19, quite a few things changed. Most notably, Graphqelm was renamed dillonkearns/elm-graphql. I believe this post is still useful but may be confusing in places because of the changes introduced in Elm 0.19. I intend to one day update this post for Elm 0.19 but please be mindful that if you follow what’s been written verbatim, it will not work. With that caveat, please enjoy this post!

Introduction

In the last post, we covered installing Graphqelm and making a simple query. In this post we’re going to look at how to pass optional arguments using Graphqelm.

An example of optional argument type in Graphql

In Github’s Graphiql explorer, when we inspect avatarUrl, we get the following details.

The ! after URI means that we will always get a url for the avatar URL. That makes sense — if a user hasn’t uploaded an avatar image, Github will use a generated avatar image.

Notice that we can pass an argument of size: Int. The absence of a ! means that we don’t have to pass a size argument. In other words, it’s an optional argument. If we omit it, Github will return us an avatar image URL with some default value.

(Note: If size were required, it would have been defined as size: Int!)

Graphql query with an optional argument

Let’s create a Graphql query that will get us the name and avatar of a Github user with a login of octocat.

query {
user (login: "octocat") {
name
avatarUrl
}
}

If we wanted to specify the size of the avatar, say 30 pixels, we would write the query like this:

query {
user (login: "octocat") {
name(size: 30)
avatarUrl
}
}

size is an optional argument because it works with or without specifying a value.

Writing the query in Elm

Let’s update the code that we used in the previous post.

Updating the User type

type alias User =
{ name : Maybe String
, avatarUrl : Github.Scalar.Uri
}

Unlike name, avatarUrl will always be returned so we don’t need to use a Maybe. We know it will always be returned because the type URI! has a ! which means Github will always give use an avatarUrl.

You may be wondering about the type Github.Scalar.Uri. Graphql has the idea of custom scalars that are defined on the server side. They’re custom because, if we were to interact with another Graphql server, that type won’t exist unless they’ve implemented it too. When we generated our Graphqelm library for Github, the Graphqelm command line app mapped the custom scalar to an Elm type.

The advantage of using this type is that it makes the type declarative. As prose, the type would read: “We have a type from Github’s Graphql API which is a custom scalar type. The custom type is a URI.”

Updating the pipeline

user : SelectionSet User Github.Object.User
user =
User.selection User
|> with User.name
|> with (User.avatarUrl identity)

The glaring thing above would be identity and we’ll get to that and what it all means in just a moment.

I’ll take a moment to mention that order is important in the pipeline. That is, the order of the piped functions must follow the order that the fields have been declared in the User type. This is another advantage of using the custom Github.Scalar.Uri type. Had we got the order incorrect and tried to map the avatar URI to name, the Elm compiler would have told us that the types didn’t match.

The identity function

Okay, now let’s talk about identity. What is it anyway?

The type signature of the identity function is shown below.

identity : a -> a

This means that it’ll return anything that you give it. identity "whatever" would return "whatever". Why would we use such a seemingly useless function?

Optional arguments in a language without optional arguments

While many programming languages natively support optional arguments, Elm is not one of them. So how does Graphqelm pull this off?

Let’s take a quick look at the function for avatar URL generated by Graphqelm. This is not the complete function but it gives us a sense of what’s going on.

avatarUrl : (AvatarUrlOptionalArguments -> AvatarUrlOptionalArguments) -> Field Github.Scalar.Uri Github.Object.User
avatarUrl fillInOptionals =
let
filledInOptionals =
fillInOptionals { size = Absent }

What’s happening is that we have a record with all the optional argument values filled in as Absent. We pass in a function to avatarUrl with a type signature of AvatarUrlOptionalArguments -> AvatarUrlOptionalArguments which updates the record. This function is captured as fillInOptionals.

If we don’t want to specify a value to the optional argument, we want to send a function that communicates “leave it alone”. And that’s why we pass in identity if we don’t want to specify a custom size for the avatar. In more literal terms, we are performing identity { size = Absent } and getting { size = Absent } back.

Setting an avatar size

Suppose we wanted to get back an avatar of size 200px. Here’s what our pipeline would look like.

import Graphqelm.OptionalArgument exposing (OptionalArgument(Present))user : SelectionSet User Github.Object.User
user =
User.selection User
|> with User.name
|> with
(User.avatarUrl
(\optionals -> { optionals | size = Present 200 })
)

You’ll notice that instead of using identity, we’ve replace it with a lambda that updates size to Present 200. A reasonable question to ask is: “Why is it Present 200? I mean, why isn’t it just 200?”

Present, Absent and Null

The type definition for OptionalArgument in Graphqelm is as follows:

type OptionalArgument a
= Present a
| Absent
| Null

So far, we’ve seen Present and Absent. Why do we have a Null type?

This is best explained by the documentation.

you could have a mutation that deletes an entry if a null argument is provided, or does nothing if the argument is absent

To summarise, we can pass in optional arguments to Graphqelm by using a function which updates a record. By default, the values in the record are Absent and we can use Present and Null as necessary. If we want to leave all the values Absent, we can use the identity function.

Displaying the avatar

Now that we have the ability to retrieve the avatar URL data from the server and bring it into our Elm app, displaying the image is straight forwards. To do this, we can create a little helper function.

profilePicture : Github.Scalar.Uri -> Html.Html Msg
profilePicture (Github.Scalar.Uri imageUrl) =
img [ src imageUrl ] []

Putting it all together

Below is the code that takes what we have covered in this post and augments it with the example presented in the previous post about Graphqelm.

And with that, we can now view the avatar image of our user!

--

--