3 ms·
This is roughly how records and extensible record arguments work in Elm. A `type alias` is a structural type, and a function argument specified as an extensible
by 1-more 1y ago
This is roughly how records and extensible record arguments work in Elm. A `type alias` is a structural type, and a function argument specified as an extensible record `{ a | … }` can be read as "any record a having at least the fields …"
type alias Furniture = { name : String, length : Float, width : Float, height : Float}
type alias Room = { name : String, length : Float, width : Float}
floorArea : { a | length : Float, width : Float} -> Float
floorArea {length, width} = length * width
This is just an example for brevity. In practice, I'd probably recommend the dimensions be stored as `Length.Length` from the package elm-units. I'd modify the extensible record argument of `floorArea` to expect the same, and I'd have `floorArea` return an `Area.Area`.
You can also use nominal typing to prohibit this via opacity. You can then wrap up construction in a function that fails for invalid states. Here we export the type Furniture but not its only constructor Furniture1.
module Furniture exposing (Furniture, width, length, height, name)
type Furniture = Furniture1 { name : String, length : Float, width : Float, height : Float}
name : Furniture -> String
name (Furniture1 {name}) = name -- etc for width, length, height
type Dimension = Width | Length | Height
type Error = NameCannotBeBlank | DimensionMustBePositive Dimension
safeConstruct : {width, height, name, length} -> Result Error Furniture
safeConstruct params =
if params.name == "" then
Err NameCannotBeBlank
else if (params.width <= 0) then
Err (DimensionsMustBePositive Width)
-- repeat for length and height
else
Ok (Furniture1 input)
then in a different module (i.e. a different file) (assume I also wrote safe constructors and accessors)
module Room exposing (Room, width, length, name)
type Room = Room1 { name : String, length : Float, width : Float}
Finally, your floor area function would not be able to inspect values of these types because we did not expose the Floor1 and Room1 constructors (in practice those would be named Floor and Room but that's confusing in this example). It would need accessors for the properties.
floorArea : { toWidth : a -> Float, toLength : a -> Float } -> a -> Float
floorArea {toWidth, toLength} = (toWidth a) * (toLength a)
floorAreaRoom : Room -> Float
floorAreaRoom = floorArea { toWidth = Room.width, length = Room.length }
floorAreaFurniture : Furniture -> Float
floorAreaFurniture = floorArea { toWidth = Furniture.width, length = Furniture.length }
Also in practice I'd maybe have `safeConstruct` have a shorter name and return a non-empty list of errors rather than just the first error, but that would cloud this example.