Pipeline builder

This is a common pattern used for decoders and validation. This pattern is used to build a function for processing some data using a series of piped functions.

type alias User =
	{ name: String
	, age: Int
	}

validateUser : User -> Result String User
validateUser user =
    Ok User
        |> validateName user.name
        |> validateAge user.age

This builds a function validateUser that will take a user and validate it. This validateUser function works like the railway pattern. We might get an Ok User at the end or an error Err String.

This pattern relies on the fact that a type alias in Elm can be used as a function. e.g. User is a function like:

String -> Int -> User

We start by putting the function (User) into a Result.

Then each function in the chain takes an attribute and the previous result, does the validation and returns a result back.

validateName : String -> Result String (String -> a) -> Result String a
validateName name =
    Result.andThen
        (\constructor ->
            if String.isEmpty name then
                Err "Invalid name"

            else
                Ok (constructor name)
        )

Complete example https://ellie-app.com/9SZTHJqB5r2a1

Caveat

When using this pattern we have to be careful with the order of functions in the pipeline. It is easy to make a mistake when the end type has many attribute of the same type.

type alias User =
	{ name: String
	, email: String
	}

With this type, we can mix up the order of name validation and email validation e.g.

    Ok User
        |> validateEmail user.email
        |> validateName user.name

This will work, but it will give us a User with the attributes mixed up:

{ name = "sam@sample.com"
, email = "Sam"
}

Some example packages using this: