Enhanced Builder Pattern, verified at compile time Question: can you design a Builder Pattern API which verifies at compile time that every field is set exactly once?

To be eligible, the size of the compiler output should not be exponential in the number of fields. The best solution will be the shortest implementation for a class of 22 fields.



Example of a possible surface API in java

Given an class for immutable data structure such as this one:

public class Human {
  private final String name;
  private final Int age;
  private final Double money;

  public Human(String name, Int age, Double money) {
    this.name = name;
    this.age = age;
    this.money = money;
  }

  public String getName() { return name; }
  public Int getAge() { return age; }
  public Double getMoney() { return money; }
}


The goal is to design an API to construct instances this Humain class with the following requirements, checked at compile time (the code in example is just a possible design, you can come up with anything that is equivalent in term of functionally):


Possible to set parameters in any order:

Human h = HumanBuilder().withName("n").withMoney(0.15).withAge(1).build();
Human g = HumanBuilder().withName("n").withAge(1).withMoney(0.15).build();

Possible to set parameters in different places of the code:

// Given appropriate takesCareOfTheMan &amp; takesCareOfTheMoney methods
Human h = takesCareOfTheMan(takesCareOfTheMoney(HumanBuilder())).build();
Human g = takesCareOfTheMoney(takesCareOfTheMan(HumanBuilder())).build();

Impossible to set the same parameter twice:

// This should not compile:
// Human h = HumanBuilder().withName("n").withAge(1).withAge(2).withMoney(0.15).build();

Impossible to omit a parameter:

// This should not compile:
// Human h = HumanBuilder().withName("n").withMoney(0.15).build();
How about something like this, in Haskell:

{-# LANGUAGE GADTs, DataKinds, KindSignatures, StandaloneDeriving #-}
module Builder ( Human', Human, mkHuman, unHuman
               , withName, withAge, withMoney
               ) where

data Optional (b :: Bool) (a :: *) where
    Missing :: Optional False a
    Present :: a -&gt; Optional True a
deriving instance (Show a) =&gt; Show (Optional b a)

data Human' b1 b2 b3 where
    MkHuman :: Optional b1 String -&gt; Optional b2 Int -&gt; Optional b3 Double -&gt; Human' b1 b2 b3
deriving instance Show (Human' b1 b2 b3)

type Human = Human' True True True

mkHuman :: Human' False False False
mkHuman = MkHuman Missing Missing Missing

unHuman :: Human -&gt; (String, Int, Double)
unHuman (MkHuman (Present name) (Present age) (Present money)) = (name, age, money)

withName :: String -&gt; Human' False b2 b3 -&gt; Human' True b2 b3
withName name (MkHuman _ age money) = MkHuman (Present name) age money

withAge :: Int -&gt; Human' b1 False b3 -&gt; Human' b1 True b3
withAge age (MkHuman name _ money) = MkHuman name (Present age) money

withMoney :: Double -&gt; Human' b1 b2 False -&gt; Human' b1 b2 True
withMoney money (MkHuman name age _) = MkHuman name age (Present money)


Example usage:

h1, h2 :: Human
h1 = withName "n" . withMoney 0.15 . withAge 1 $ mkHuman
h2 = withName "n" . withAge 1 . withMoney 0.15 $ mkHuman

takesCareOfTheName :: Human' False b2 b3 -&gt; Human' True b2 b3
takesCareOfTheName = withName "John"

-- This doesn't typecheck
h3 :: Human
h3 = withName "newName" h1

-- Neither does this
h4 :: Human
h4 = withName "n" . withAge 1 $ mkHuman