So have gotten to record in my F# journey and at first they seem rather dangerous. At first this seemed clever:
type Card = { Name : string;
Phone : string;
Ok : bool }
let cardA = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }
The idea that the cardA is patten matched with Card. Not to mention the simplified pattern matching here:
let withTrueOk =
list
|> Seq.filter
(function
| { Ok = true} -> true
| _ -> false
)
Problem is:
type Card = { Name : string;
Phone : string;
Ok : bool }
type CardTwo = { Name : string;
Phone : string;
Ok : bool }
let cardA = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }
cardA is now of CardTwo type which I am guessing has to do with F# running everything in order.
Now this might be an impossible situation since there may never be a chance of the same signature taking on two type, but it is a possibility.
Is recording something that has only limited use or am I just over thinking this one?
They are not dangerous and they are not only for limited use.
I think it's very rare that you would have two types with the same members. But if you do encounter that situation, you can qualify the record type you want to use:
let cardA = { Card.Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }
Records are very useful for creating (mostly) immutable data structures. And the fact that you can easily create a copy with just some fields changed is great too:
let cardB = { cardA with Ok = true }
I agree, record fields as members of the enclosing module/namespace seems odd at first coming from more traditional OO languages. But F# provides a fair amount of flexibility here. I think you'll find only contrived circumstances cause problems, such as two records that
The first case should never happen. The latter could be solved by record B having a field of record A.
You only need one field to be different for the two to be distinguishable. Other than that, the definitions can be the same.
type Card =
{ Name : string
Phone: string
Ok : bool }
type CardTwo =
{ Name : string
Phone: string
Age : int }
let card = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }
let cardTwo = { Name = "Alf" ; Phone = "(206) 555-0157" ; Age = 21 }
Pattern matching is also quite flexible as you only need to match on enough fields to distinguish it from other types.
let readCard card =
match card with
| { Ok = false } -> () //OK
| { Age = 21 } -> () //ERROR: 'card' already inferred as Card, but pattern implies CardTwo
Incidentally, your scenario is easily fixed with a type annotation:
let cardA : Card = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }
In order that you appreciate what F# provides, I just want to mention that there is no fully qualified accessor for records in OCaml. Therefore, to distinguish between record types with the same fields, you have to put them into submodules and reference them using module prefixes.
So your situation in F# is much better. Any ambiguity between similar record types could be resolved quickly using record accessor:
type Card = { Name: string;
Phone: string;
Ok: bool }
type CardSmall = { Address: string;
Ok: bool }
let withTrueOk list =
list
|> Seq.filter (function
| { Card.Ok = true} -> true (* ambiguity could happen here *)
| _ -> false)
Moreover, F# record is not limited at all. It provides a lot of nice features out-of-the-box including pattern matching, default immutability, structural equality, etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With