A Slight Delight - Compile-time checking things

Underjord is an artisanal consultancy doing consulting in Elixir, Nerves with an accidental speciality in marketing and outreach. If you like the writing you should really try the pro version.

This was a short-but-sweet thing that struck me while working with a client code-base. It was trivial but both useful and delightful and it is a type of thing I haven't been able to do in Python, PHP and Javascript in quite the same way.

Initially, the code was something like this:

elixir
defmodule MyApp.Schemas do 
    alias ExJson.Schema

    def my_schema do 
        %{
            "properties" => %{
                "email" => %{
                    "type": "string", 
                },
                "password" => %{
                    "type": "string", 
                }
            }
        } |> Schema.resolve()
    end
end

So for this JSON schema library the Schema.resolve function verifies that your JSON schema is defined in a valid manner and lives up to the JSON schema specification. So it doesn't do anything related to user-provided data or parsing JSON data. While familiarizing myself with the library I saw that you should probably avoid calling resolve repeatedly. Which makes sense, if the schema doesn't change, why call it again. Some previous developer probably missed that. It happens.

What I ended up doing was:

elixir
    defmodule MyApp.Schemas do 
        alias ExJson.Schema
    
        @my_schema Schema.resolve(%{
            "properties" => %{
                "email" => %{
                    "type": "string", 
                },
                "password" => %{
                    "type": "string", 
                }
            }
        })

        def my_schema do 
            @my_schema
        end
    end

So what does this give us? Well, module attributes are resolved at compile-time. This is why @my_secret System.get_env("MY_SECRET") often leads to unwanted behavior. But in this case, our schemas are part of our code and will not change at run-time or between environments. So we can shift the resolution to happen at compile-time and never have to do it at run-time.

  • The cost of calling Schema.resolve is paid up front at compile-time.
  • Errors in defining JSON schemas show up at compile-time rather than run-time when you attempt to validate something.

Having put much less time into compiled languages this was delightful. Being able to shift some checks back to compile-time is incredibly useful. I've also had a reason to write some macros which was fun.

If you have some suggestions, corrections or thoughts on this let me know at lars@underjord.io.

Underjord is an artisanal consultancy doing consulting in Elixir, Nerves with an accidental speciality in marketing and outreach. If you like the writing you should really try the pro version.

Note: Or try the videos on the YouTube channel.