简体   繁体   中英

What is the easiest way of creating a value of a large F# record?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM