Suppose that I am building a company website. I need to receive the information of the four branch offices, such as location, business hours, telephone number. My initial design would be like this:
type Branch
= Lyon BranchInfo
| Cordova BranchInfo
| Sofia BranchInfo
| Baku BranchInfo
type alias BranchInfo =
{ location: Location
, businessHours : (Hour, Hour)
, phoneNumber: PhoneNumber
}
The data should be coming in as a list in a JSON.
{ "branches" :
[ { "name" : "Lyon",
"address" : "86 Rue Pasteur, 69007 Lyon, France",
"businessHours" : "09:00-16:00",
"phone" : "+33 4 78 69 70 00"
},
...
]
}
I could decode it into a list like:
branches : List Branch
branches = [ Lyon {...}, Cordova {...}, Sofia {...}, Baku {...} ]
The JSON data should and will contain info for all of the branches. Unfortunately, the compiler cannot know that from the List
type and will stoically give me a Maybe
type whenever I try to fetch the data for a particular branch.
The question is, how can I convince the compiler that the decoded data contains 4 items and has complete set of data for all branches? What kind of collection type could convince compiler of such property?
Here’s my first attempt:
type alias Branches =
{ lyon : Branch
, cordova : Branch
, sofia : Branch
, baku : Branch
}
Above type can be used to guarantee that the data for four branches are there. But it cannot prevent accidental mixup of Branch instance.
Then here’s the second attempt
-- Branch.elm
type Branch
= Lyon
| Cordova
| Sofia
| Baku
type alias Branches =
{ lyon : Branch.Lyon.LyonBranch
, cordova : Branch.Lyon.CordovaBranch
, sofia : Branch.Lyon.SofiaBranch
, baku : Branch.Lyon.BakuBranch
}
-- Branch.Lyon.elm
type LyonBranch = LyonBranch BranchInfo
getBranchInfo ( LyonBranch branchInfo) = branchInfo
-- Branch.Cordova.elm
type CordovaBranch = CordovaBranch BranchInfo
-- Branch.Sofia.elm
type SofiaBranch = SofiaBranch BranchInfo
-- Branch.Baku.elm
type BakuBranch = BakuBranch BranchInfo
-- View.elm
view : Model -> Html msg
view model =
div [] [ List.map (viewBranch model.branches) [ Lyon, Cordova, Baku, Sofia] ]
viewBranch : Branches -> Branch -> Html msg
viewBranch branches branch =
case branch of
Lyon -> branches |> .lyon >> Branch.Lyon.getBranchInfo >> viewBranchInfo
...
This ensures that there are four branches, and each branch is annotated as a distinct type. It still cannot prevent mixup of BranchInfo data, but I think that’s unavoidable (I’d love to be proven wrong about this!).
It’s a bit more verbose than I would like and the names are repetitive and dull. Moreover, I have to use Branches
record as a sort of database and use Branch
type like a cumbersome key to retrieve data. I would prefer the Branch
type to contain all the information about itself.
Or I could use Dict
instead of record. I could decode the JSON into a Dict
and let the decoding succeed if it has all four keys. Unfortunately, the keys would have to be hardcoded since it’s impossible to iterate over constructors in Elm.
-- Branch.elm
type Branch
= Lyon
| Cordova
| Sofia
| Baku
type Branches = Branches (Dict String BranchInfo)
-- public
getInfo : Branch -> Branches -> BranchInfo
getInfo branch branches = map (Dict.get (toString branch)) branches |> Maybe.withDefault defaultBranchInfo
-- private
map f (Branches dict) = f dict
-- View.elm
viewBranch : Branches -> Branch -> Html msg
viewBranch branches branch =
case branch of
LyonBranch -> getLyon
...
This is much shorter, and although I make sure that the data has valid properties when decoding JSON, I still feel like I am cheating the type system by using Maybe.withDefault
like this.
Overall, I’m not fond of either approach. If someone could show me a better way to do this in Elm, I’d really appreciate!