Genom att programmera mot ett gränssnitt/interface möjligör man att koden på båda sidorna kan utvecklas separat så länge båda sidor uppfyller de krav som gränssnittet ställer. Här är ett krystat exempel med 3 varianter av samma "komponent" (filerna finns längre ner).
Används såhär:
Coupled.view "mt-12" model.accounts
|> Html.map AccountWasClickedDenna modulen importerar Account, den är tight kopplad till den konkreta typen Account.Account. Den går inte att använda med någonting annat än Account. Om Account.Account uppdateras/ändras så behöver eventuellt även modulen Coupled uppdateras ändras. Detsamma gäller det indirekta beroendet på Money.
Används såhär:
NotSoCoupled.view "mt-12" (List.map accountToRow model.accounts)
accountToRow : Account -> NotSoCoupled.Row Msg
accountToRow account =
{ id = String.fromInt account.id
, onClick = AccountWasClicked account
, text = String.join " " [ account.name, Money.toString account.balance, "kr" ]
}Denna modulen har definerat ett API som konsumerande kod förväntas uppfylla. Det medför en större börda på den konsumerande koden att implementera den del av koden överbryggar glappet mellan den konkreta typen, Account, och det gränssnitt som komponenten definerat, Row msg. Detta kommer med fördelen att man kan utveckla NotSoCoupled utan kännedom om den konreta typen, vilket underlättar testbarhet och lokal utveckling.
Används såhär:
NotSoCoupled2.view config model.accounts
config : NotSoCoupled2.Config Account Msg
config =
{ id = .id >> String.fromInt
, onClick = AccountWasClicked
, text = \account -> String.join " " [ account.name, Money.toString account.balance, "kr" ]
, extraClass = "mt-12"
}Detta är en variant på NotSoCoupled som är i stort sett likvärdig.