Designing Opaque Type for form fields in Elm: Part 1
Tags: elm, architecture, webdev
Published: 2020-04-08 14:36:53

---
title: Designing Opaque Type for form fields in Elm: Part 1
published: true
description: This is an article that shares you all my knowledge on designing Opaque Type for forms fields.
tags: Elm, architecture, webdev
series: Designing Opaque Type for form fields in Elm
---

This is an article that shares my knowledge on designing Opaque Type for form fields.

Let's say, we have this kind of a simple form.

```elm
-- A simple String value form


type alias Model =
    { username : String }


init : Model 
init =
    { username = "" }


type Msg
    = UsernameInputted String


update : Msg -> Model -> Model
update msg =
    case msg of
        UsernameInputted value ->
            { username = value }
```

This is pretty much fine for now, but one day we feel like to add validation to `username` that it must be more than 5 characters and less than 20 characters. 

# Opaque Type for validations

Now, this is the time to use Opaque Type to encapsulate validation logic into the separated module. We introduce `Username` type which wraps `String` as its internal data.

```elm
type Username =
    Username String


new : String -> Maybe Username
new value =
    if String.length value > 20 then
        Nothing

    else if String.length value < 5 then
        Nothing

    else
        Just (Username value)
```
`new` function now here is a point that does validation of inputted `String`. If the inputted data is valid, it returns `Just`.

Okay, now it seems that we can start using `Username` as a field value like below.

```elm
type alias Model =
    { username : Username }
```

However, it is impossible actually. 

# Design failure

The main reason is time to run validation. In current design, we have validation mechanism in `new` function so that it always works when `Username` data is initialized with `String` value. The time to initialize  it is that `update` function handling message attached to `onInput` event coming from views, so `Username` initialization is going to be triggered every single time when users type their keyboard to fill the field.

It means that, even though the users don't finish typing their value on the field, **validation errors will pop up, because the value they are typing partially to fill the field is always invalid**. Errors will appear from the beginning. This is so annoying!

So, in case of this, we need to change timing to trigger off validation. The thing to keep in mind is that, **Opaque Type for input fields will have "partial" state which describes "users don't need to get validation at this moment"**. Don't start validation from the very beginning, but do it right once users finished their typing on the field.

Let's see how we can make it better!

# Improvement

Now, `Username` type has three patterns. Every variant has String that describes the current value of it. When the data is `Partial`, it means that the data conveyed on it does not get validated.

```elm
module Username exposing (Username, Error(..), empty, error)


type Username
    = Partial String
    | Valid String
    | Invalid String Error


type Error
    = LengthTooLong
    | LengthTooShort


empty : Username
empty =
    Partial ""


error : Username -> Maybe Error
error username =
    case username of
        Invalid _ error ->
            Just error

        _ ->
            Nothing
```

The intitial state of `Username` is always `Partial` as `empty` functions shows. It will not be changed until users finished filling value of it. 

Then, when is `Partial` changed into other two variants? That's exactly what `view` and `blur` function do.

The moment to start validation is when users removed focus from input fields, so this module provides `blur` function for it. `blur` function is expected to be used in `update` function handling `onBlur` event. It triggers off validation at once.

On the contrary, the handler for `onInput` event does not trigger validation during `Partial` is given to it. It always is just waiting that `blur` function gets called. Once validation has started by `blur` function, the handler for `onInput` triggers off validation every time!

```elm
module Username exposing (Username, Error(..), empty, error, input, blur)

import Browser
import Html exposing (Html)
import Html.Attributes exposing (type_, value)
import Html.Events exposing (onBlur, onInput)


-- ...


input : (Username -> msg) -> msg -> Username -> Html msg
input onInputMsg onBlurMsg username =
    let
        onInputHandler =
            \value ->
                onInputMsg <|
                    case username of
                        Partial _ ->
                            Partial value

                        _ ->
                            validate value
    in
    Html.input
        [ type_ "text"
        , value <| toString username
        , onBlur onBlurMsg
        , onInput onInputHandler
        ]
        []


blur : Username -> Username
blur username =
    case username of
        Partial value ->
            validate value

        _ ->
            username


-- internals


validate : String -> Username
validate value =
    if String.length value > 20 then
        Invalid value LengthTooLong

    else if String.length value < 5 then
        Invalid value LengthTooShort

    else
        Valid value


toString : Username -> String
toString username =
    case username of
        Partial value ->
            value

        Valid value ->
            value

        Invalid value _ ->
            value
```
`validate` function and `toString` function are pretty simple. They two probably have no need to be described. One is just to do validation, and the other is to extract `String` from `Username` type.


# Wire it up!

Now, we don't have to care timing or anything in converting a primitive value of `String` into `Username`. It all is completely handled by `Username` module itself. Gee!

```elm
module App exposing (main)

import Html exposing (Html, div)
import Username


type alias Model =
    { username : Username.Username }


init : Model
init =
    { username = Username.empty }


type Msg
    = UsernameInputted Username.Username
    | UsernameBlurred


update : Msg -> Model -> Model
update msg model =
    case msg of
        UsernameInputted username ->
            { username = username }

        UsernameBlurred ->
            { username = Username.blur model.username }


view : Model -> Html Msg
view model =
    let
        error =
            model.username
                |> Username.error
                |> Maybe.withDefault ""
    in
    div
        []
        [ Username.input UsernameInputted UsernameBlurred model.username
        , div [] [ text error ]
        ]


main : Program () Model Msg
main =
    Browser.sandbox
        { init = init
        , view = view
        , update = update
        }
```

One-file demo on Ellie is [here](https://ellie-app.com/8xs2jZmhfGSa1)