为多种数据类型制作基于表单的应用程序时避免F#中的代码重复

为多种数据类型制作基于表单的应用程序时避免F#中的代码重复,f#,code-readability,fable-f#,elmish,F#,Code Readability,Fable F#,Elmish,我目前正在F#中制作一个应用程序,采用fable elmish体系结构,其中记录类型如下(减少以节省空间,但希望您能理解) 现在我已经将它们编译成了一个区别联合类型,比如 type NewEntry = | NewOriginMerdEntry of NewOriginMerdEntry | NewTreatmentEntry of NewTreatmentEntry | NewDestMerdEntry of NewDestMerdEntry ...etc 最

我目前正在F#中制作一个应用程序,采用fable elmish体系结构,其中记录类型如下(减少以节省空间,但希望您能理解)

现在我已经将它们编译成了一个区别联合类型,比如

type NewEntry =
    | NewOriginMerdEntry of NewOriginMerdEntry
    | NewTreatmentEntry of NewTreatmentEntry
    | NewDestMerdEntry of NewDestMerdEntry
    ...etc
最后,主消息类型如下所示:

type Msg = {
     NewEntry of NewEntry
}
let originMerdView (dispatch : Msg -> unit) (model : Model) =
    let dispatch' = NewOriginMerdEntry >> NewEntry >> dispatch
    let form = match model.form with
                | OriginMerd o -> o
                | _ -> None

    R.scrollView[
        P.ViewProperties.Style [
            P.FlexStyle.FlexGrow 1.
            P.BackgroundColor "#000000"
        ]
    ][
        //these functions are simply calls to various input text boxes
        inputText "ID" AddOriginMerdID dispatch'
        numinputText "MerdNumber" AddMerdNumber dispatch'
        floatinputText "average Weight" AddAverageWeight dispatch'
        numinputText "PD" AddPD dispatch'
        button "save" form SaveOriginMerd (SaveEntry >> dispatch)
    ]


let inputText label msg dispatch =


    R.textInput[

        P.TextInput.OnChangeText ( msg >> dispatch )
    ]
let handleNewEntry (model : Model) (msg : NewEntry) =
    let form' =
        match msg, model.form with
        | NewOriginMerdEntry m, OriginMerd o -> handleNewOriginMerdEntry o m
        | NewOriginMerdEntry m, _ -> failwithf "expected origin type got something else"
        | NewDestMerdEntry m, DestMerd d -> handleNewDestMerdEntry d m
        | NewDestMerdEntry m, _ -> failwithf "expected dest type got something else"
    {model with form = form'}, Cmd.none

let handleNewOriginMerdEntry (formOpt : OriginMerdEntry option) (msg : NewOriginMerdEntry) =
    let form = formOpt |> Option.defaultValue OriginMerd.New
    let result =
        match msg with
        | AddOriginMerdID i -> {form with originMerdID = i}
        | AddMerdNumber n -> {form with merdNumber = n}
        | AddPD p -> {form with pD = p}
        | AddAverageWeight w -> {form with averageWeight = w}
    OriginMerd (Some result)

let handleNewDestMerdEntry (formOpt : DestMerdEntry option) (msg : NewDestMerdEntry) =
    let form = formOpt |> Option.defaultValue DestMerd.New
    let result =
        match msg with
        | AddDestMerdID i -> {form with destMerdID = i}
    DestMerd (Some result)
这已经足够干净了,但是在视图函数中,我需要为每种类型创建一个新函数,这些类型表示视图以及文本输入更改时需要发送的特定消息

是这样的:

type Msg = {
     NewEntry of NewEntry
}
let originMerdView (dispatch : Msg -> unit) (model : Model) =
    let dispatch' = NewOriginMerdEntry >> NewEntry >> dispatch
    let form = match model.form with
                | OriginMerd o -> o
                | _ -> None

    R.scrollView[
        P.ViewProperties.Style [
            P.FlexStyle.FlexGrow 1.
            P.BackgroundColor "#000000"
        ]
    ][
        //these functions are simply calls to various input text boxes
        inputText "ID" AddOriginMerdID dispatch'
        numinputText "MerdNumber" AddMerdNumber dispatch'
        floatinputText "average Weight" AddAverageWeight dispatch'
        numinputText "PD" AddPD dispatch'
        button "save" form SaveOriginMerd (SaveEntry >> dispatch)
    ]


let inputText label msg dispatch =


    R.textInput[

        P.TextInput.OnChangeText ( msg >> dispatch )
    ]
let handleNewEntry (model : Model) (msg : NewEntry) =
    let form' =
        match msg, model.form with
        | NewOriginMerdEntry m, OriginMerd o -> handleNewOriginMerdEntry o m
        | NewOriginMerdEntry m, _ -> failwithf "expected origin type got something else"
        | NewDestMerdEntry m, DestMerd d -> handleNewDestMerdEntry d m
        | NewDestMerdEntry m, _ -> failwithf "expected dest type got something else"
    {model with form = form'}, Cmd.none

let handleNewOriginMerdEntry (formOpt : OriginMerdEntry option) (msg : NewOriginMerdEntry) =
    let form = formOpt |> Option.defaultValue OriginMerd.New
    let result =
        match msg with
        | AddOriginMerdID i -> {form with originMerdID = i}
        | AddMerdNumber n -> {form with merdNumber = n}
        | AddPD p -> {form with pD = p}
        | AddAverageWeight w -> {form with averageWeight = w}
    OriginMerd (Some result)

let handleNewDestMerdEntry (formOpt : DestMerdEntry option) (msg : NewDestMerdEntry) =
    let form = formOpt |> Option.defaultValue DestMerd.New
    let result =
        match msg with
        | AddDestMerdID i -> {form with destMerdID = i}
    DestMerd (Some result)
因此,第一个问题是,是否有可能以某种方式概括这一点,因为mainview将根据模型状态决定运行这些函数中的哪一个。它工作得很好,但是代码重复的数量是相当痛苦的

此外,每个新条目都将发送到此函数:

let handleNewEntry (model : Model) (entry : NewEntry) =
    match entry with
    | NewOriginMerdEntry e -> handleNewOriginMerdEntry model e
    ... etc


let handleNewOriginMerdEntry (model : Model) (entry : NewOriginMerdEntry) =
    let form =
        match model.form with
        | OriginMerd o -> match o with
                            | Some f -> f
                            | None -> OriginMerd.New
        | _ -> failwithf "expected origin type got something else handleNewOriginMerd"

    let entry =
        match entry with
        | AddOriginMerdID i -> {form with originMerdID = i}
        | AddMerdNumber n -> {form with merdNumber = n}
        | AddPD p -> {form with pD = p}
        | AddAverageWeight w -> {form with averageWeight = w}

    {model with form = OriginMerd (Some entry)}, Cmd.none

所有特定的句柄新输入函数都完全相同,只是记录明显不同。这很好,但代码重用也是非常痛苦的。是否有更优雅的方法以更少的代码重复来实现相同的结果?

在我看来,至少您的这一部分观点将被分享:

let form = match model.form with
           | OriginMerd o -> o  // With a different match target each time
           | _ -> None

R.scrollView[
    P.ViewProperties.Style [
        P.FlexStyle.FlexGrow 1.
        P.BackgroundColor "#000000"
    ]
]
我想你可以把它拉到它自己的函数中,让输入字段的列表不同。而且,至关重要的是,将模型的一部分传递给这些函数。例如:

let originMerdForm (dispatch : Msg -> unit) (OriginMerd form) =
    let dispatch' = NewOriginMerdEntry >> NewEntry >> dispatch
    [
        //these functions are simply calls to various input text boxes
        inputText "ID" AddOriginMerdID dispatch'
        numinputText "MerdNumber" AddMerdNumber dispatch'
        floatinputText "average Weight" AddAverageWeight dispatch'
        numinputText "PD" AddPD dispatch'
        button "save" form SaveOriginMerd (SaveEntry >> dispatch)
    ]

let destMerdForm (dispatch : Msg -> unit) (DestMerd form) =
    let dispatch' = NewDestMerdEntry >> NewEntry >> dispatch
    [
        inputText "ID" AddDestMerdID dispatch'
        button "save" form SaveDestMerd (SaveEntry >> dispatch)
    ]

let getFormFields (model : Model) =
    match model.form with
    | OriginMerd _ -> originMerdForm model.form
    | DestMerd _ -> destMerdForm model.form
    // etc.
    | _ -> []

let commonView (dispatch : Msg -> unit) (model : Model) =
    R.scrollView[
        P.ViewProperties.Style [
            P.FlexStyle.FlexGrow 1.
            P.BackgroundColor "#000000"
        ]
    ] (getFormFields model)
注意,按照我写这篇文章的方式,您将在相应函数的
OriginMerd表单
DestMerd表单
部分得到一个“不完全匹配案例”警告。我真正想要的是让他们拥有条目的类型(您原始代码的
OriginMerd o
行中的
o
),但我不知道您将其命名为什么。唯一需要做的更改是,您希望提取对公共视图的
按钮调用,例如

let commonView (dispatch : Msg -> unit) (model : Model) =
    let formFields, saveMsg = getFormFields model
    R.scrollView[
        P.ViewProperties.Style [
            P.FlexStyle.FlexGrow 1.
            P.BackgroundColor "#000000"
        ]
    ] (formFields @ [button "save" model.form saveMsg (SaveEntry >> dispatch))
然后您的
originemerdform
destMerdForm
将返回
(表单字段,msg)
的元组,其中
msg
将是
saveoriginemerd
SaveDestMerd
,等等

您的
handlenewfootery
函数也可以从输入参数的类似更改中获益:您可以只传入适当的条目类型(并将
entry
参数重命名为
msg
,以免混淆自己)。也就是说,它看起来像这样:

type Msg = {
     NewEntry of NewEntry
}
let originMerdView (dispatch : Msg -> unit) (model : Model) =
    let dispatch' = NewOriginMerdEntry >> NewEntry >> dispatch
    let form = match model.form with
                | OriginMerd o -> o
                | _ -> None

    R.scrollView[
        P.ViewProperties.Style [
            P.FlexStyle.FlexGrow 1.
            P.BackgroundColor "#000000"
        ]
    ][
        //these functions are simply calls to various input text boxes
        inputText "ID" AddOriginMerdID dispatch'
        numinputText "MerdNumber" AddMerdNumber dispatch'
        floatinputText "average Weight" AddAverageWeight dispatch'
        numinputText "PD" AddPD dispatch'
        button "save" form SaveOriginMerd (SaveEntry >> dispatch)
    ]


let inputText label msg dispatch =


    R.textInput[

        P.TextInput.OnChangeText ( msg >> dispatch )
    ]
let handleNewEntry (model : Model) (msg : NewEntry) =
    let form' =
        match msg, model.form with
        | NewOriginMerdEntry m, OriginMerd o -> handleNewOriginMerdEntry o m
        | NewOriginMerdEntry m, _ -> failwithf "expected origin type got something else"
        | NewDestMerdEntry m, DestMerd d -> handleNewDestMerdEntry d m
        | NewDestMerdEntry m, _ -> failwithf "expected dest type got something else"
    {model with form = form'}, Cmd.none

let handleNewOriginMerdEntry (formOpt : OriginMerdEntry option) (msg : NewOriginMerdEntry) =
    let form = formOpt |> Option.defaultValue OriginMerd.New
    let result =
        match msg with
        | AddOriginMerdID i -> {form with originMerdID = i}
        | AddMerdNumber n -> {form with merdNumber = n}
        | AddPD p -> {form with pD = p}
        | AddAverageWeight w -> {form with averageWeight = w}
    OriginMerd (Some result)

let handleNewDestMerdEntry (formOpt : DestMerdEntry option) (msg : NewDestMerdEntry) =
    let form = formOpt |> Option.defaultValue DestMerd.New
    let result =
        match msg with
        | AddDestMerdID i -> {form with destMerdID = i}
    DestMerd (Some result)
每当你说,“嘿,这里有很多重复”,通常有一种方法可以将它提取到一个公共函数中。F#type系统是您的朋友:当您深入到这样的重构中时,您不会总是记得您已经更改了哪些函数,哪些没有更改。不过,只要看看红色的波浪线,你就会知道哪些函数还需要处理。希望这个例子能启发您发现其他可以提取到其自身函数中的常见代码