首页 > 解决方案 > 如何压缩这个重复的 F# 代码?

问题描述

编辑:底部可能的解决方案

我正在做一些数据工作,我需要非常小心最终将在固定宽度文本输出中发送的字符串长度,存储在有限大小的 nvarchar 字段等中。我希望对这些而不是裸系统进行良好的严格输入.String 类型。

假设我有一些这样的代码来表示这些,有一些有用的模块函数可以很好地与 Result.map、Option.map 等配合使用。

module String40 =
    let private MaxLength = 40
    type T = private T of string
    let create (s:string) = checkStringLength MaxLength s |> Result.map T
    let trustCreate (s:string) = checkStringLength MaxLength s  |> Result.okVal |> T
    let truncateCreate (s:string) = truncateStringToLength MaxLength s |> T
    let toString (T s) = s

    type T with
        member this.AsString = this |> toString


module String100 =
    let private MaxLength = 100
    type T = private T of string
    let create (s:string) = checkStringLength MaxLength s |> Result.map T
    let trustCreate (s:string) = checkStringLength MaxLength s  |> Result.okVal |> T
    let truncateCreate (s:string) = truncateStringToLength MaxLength s |> T
    let toString (T s) = s

    type T with
        member this.AsString = this |> toString

显然,这些几乎是完全重复的,只是每个块中的模块名称和最大长度不同。

有哪些选择可以尝试减少这里的重复性?我很想拥有这样的东西:

type String40 = LengthLimitedString<40>
type String100 = LengthLimitedString<100>

tryToRetrieveString ()   // returns Result<string, ERRType>
|> Result.bind String40.create

这些代码块相当短,对于不同的字段长度只需复制/粘贴十几次就不会是世界末日。但我觉得我错过了一种更简单的方法来做到这一点。

更新:

另一个注意事项是,使用这些类型的记录提供有关它们所携带的类型的信息很重要:

type MyRecord = 
  {
    FirstName: String40;
    LastName: String100;
  }

而不是像

type MyRecord = 
  {
    FirstName: LimitedString;
    LastName: LimitedString;
  }


Tomas 的回答是,使用 Depended Type Provider nuget 库是一个很好的解决方案,对于许多对其行为很好的人来说,这将是一个很好的解决方案。我觉得扩展和自定义会有点棘手,除非我想维护我自己希望避免的类型提供程序的副本。

Marcelo 关于静态参数约束的建议是一条颇有成效的研究路径。它们基本上给了我我一直在寻找的东西——一个基本上是静态方法的“接口”的通用参数。然而,更重要的是它们需要内联函数才能运行,而我没有时间评估这在我的代码库中有多少重要或不重要。

但是我接受了它并将其更改为使用常规的通用约束。必须实例化一个对象以获得最大长度值有点愚蠢,而且 fsharp 类型/通用代码看起来很粗糙,但从模块用户的角度来看它是干净的,我可以很容易地扩展它我想要的。

    type IMaxLengthProvider = abstract member GetMaxLength: unit -> int

    type MaxLength3 () = interface IMaxLengthProvider with member this.GetMaxLength () = 3
    type MaxLength4 () = interface IMaxLengthProvider with member this.GetMaxLength () = 4


    module LimitedString =

        type T< 'a when 'a :> IMaxLengthProvider> = private T of string

        let create< 't when 't :> IMaxLengthProvider and 't : (new:unit -> 't)> (s:string) =
            let len = (new 't()).GetMaxLength()
            match checkStringLength len s with
            | Ok s ->
                let x : T< 't> = s |> T
                x |> Ok
            | Error e -> Error e
        let trustCreate< 't when 't :> IMaxLengthProvider and 't : (new:unit -> 't)> (s:string) =
            let len = (new 't()).GetMaxLength()
            match checkStringLength len s with
            | Ok s ->
                let x : T< 't> = s |> T
                x
            | Error e -> 
                let msg = e |> formErrorMessage
                failwith msg

        let truncateCreate< 't when 't :> IMaxLengthProvider and 't : (new:unit -> 't)> (s:string) =
            let len = (new 't()).GetMaxLength()
            let s = truncateStringToLength len s
            let x : T< 't> = s |> T
            x

        let toString (T s) = s
        type T< 'a when 'a :> IMaxLengthProvider> with
            member this.AsString = this |> toString


    module test =
        let dotest () =

            let getString () = "asd" |> Ok

            let mystr = 
                getString ()
                |> Result.bind LimitedString.create<MaxLength3>
                |> Result.okVal
                |> LimitedString.toString

            sprintf "it is %s" mystr

标签: f#

解决方案


使用一点仔细的反射魔法,我们可以实现很多并获得一些非常好的类型。这样的事情怎么样?

module Strings =
    type [<AbstractClass>] Length(value: int) =
        member this.Value = value

    let getLengthInst<'L when 'L :> Length> : 'L =
        downcast typeof<'L>.GetConstructor([||]).Invoke([||])

    type LimitedString<'Length when 'Length :> Length> =
        private | LimitedString of maxLength: 'Length * value: string

        member this.Value =
            let (LimitedString(_, value)) = this in value
        member this.MaxLength =
            let (LimitedString(maxLength, _)) = this in maxLength.Value

    module LimitedString =
        let checkStringLength<'L when 'L :> Length> (str: string) =
            let maxLength = getLengthInst<'L>.Value
            if str.Length <= maxLength then Ok str
            else Error (sprintf "String of length %d exceeded max length of %d" str.Length maxLength)

        let create<'L when 'L :> Length> (str: string) =
            checkStringLength<'L> str
            |> Result.map (fun str -> LimitedString (getLengthInst<'L>, str))

open Strings

// === Usage ===

type Len5() = inherit Length(5)
type Len1() = inherit Length(1)

// Ok
LimitedString.create<Len5> "Hello"
// Error
LimitedString.create<Len1> "world"

推荐阅读