I'm learning F# and I want to know if I'm approaching it the right way. I created a record type that contain 6 labels but it can easily grow to 10+ labels. How should I design its constructor? Since record values are immutable, I think I should create in one go. Each parameter in the constructor map to a label when constructing the value. However, this just feels wrong.
For example:
module Deal =
open System
type T = {
Title : string;
Description: string;
NumberOfVotes: int;
NumberOfComments: int;
DateCreated: DateTime;
ImageUrl: string;
}
let Create (title:string) (desc:string) (numberOfVotes:int) (numberOfComments:int) (dateCreated: DateTime) (imageUrl:string) =
{
Title = title;
Description = desc;
NumberOfVotes = numberOfVotes;
NumberOfComments = numberOfComments;
DateCreated = dateCreated;
ImageUrl = imageUrl;
}
I don't think the constructor scales nicely as more labels are added to the record.
Is there a better solution?
In the F# for fun and profit example the email address being stored is a string and it's in a wrapper type (a discriminated union is used). You don't seem to have a wrapper type. This is how I would adapt the example to your case:
open System
type Deal =
{ Title : string; Description: string; NumberOfVotes: int
NumberOfComments: int; DateCreated: DateTime; ImageUrl: string }
module Deal =
type ValidDeal =
private ValidDeal of Deal // Note the `private` on the case constructor
let isValid deal = true // Add implementation
let create deal =
if isValid deal then Some (ValidDeal deal)
else None
let (|ValidDeal|) (ValidDeal deal) = deal // This is an active pattern
open Deal
let f (ValidDeal d) = d // Use the active pattern to access the Deal itself.
let g d = ValidDeal d // Compile error. The ValidDeal union case constructor is private
Note that the ValidDeal
union case constructor is private to the Deal
module, which also contains the isValid
function. So this module has complete control over over the validation of deals. Any code outside of this module must use Deal.create
to create a valid deal and if any function receives a ValidDeal
there is something of a compile time guarantee of validity.
Record construction generally does not scale very well: You will need to add the additional fields for every construction expression. If you can assign sensible default values to your additional fields, a constructor with optional arguments may help.
open System
type Deal =
{ Title : string; Description: string; NumberOfVotes: int
NumberOfComments: int; DateCreated: DateTime; ImageUrl: string } with
static member Create
( ?title : string,
?desc : string,
?numberOfVotes : int,
?numberOfComments : int,
?dateCreated: DateTime,
?imageUrl : string ) =
{ Title = defaultArg title "No title"
Description = defaultArg desc "No description"
NumberOfVotes = defaultArg numberOfVotes 0
NumberOfComments = defaultArg numberOfComments 0
DateCreated = defaultArg dateCreated DateTime.Now
ImageUrl = defaultArg imageUrl "Default.jpg" }
While being even more boilerplate, additional fields can be introduced without impacting constructor call sites. There are two ways to supply specific arguments, either named arguments or F#'s copy-and-update record expressions.
Deal.Create(title = "My Deal") // named argument
{ Deal.Create() with NumberOfVotes = 42 } // copy-and-update
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.