简体   繁体   English

创建大型F#记录值的最简单方法是什么?

[英]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. 我正在学习F#,我想知道我是否正确地接近它。 I created a record type that contain 6 labels but it can easily grow to 10+ labels. 我创建了一个包含6个标签的记录类型,但它可以很容易地增长到10个以上的标签。 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). F#for fun and profit示例中,存储的电子邮件地址是一个字符串,它是一个包装类型(使用了一个有区别的联合)。 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. 请注意, ValidDeal union case构造函数对Deal模块是私有的,该模块还包含isValid函数。 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. 此模块之外的任何代码都必须使用Deal.create来创建有效的交易,如果任何函数收到ValidDeal ,则有一些编译时保证有效性。

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. 有两种方法可以提供特定的参数,可以是命名参数,也可以是F#的复制和更新记录表达式。

Deal.Create(title = "My Deal")  // named argument
{ Deal.Create() with NumberOfVotes = 42 }   // copy-and-update

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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