代码之家  ›  专栏  ›  技术社区  ›  Clyde

如何压缩这个重复的F代码?

f#
  •  2
  • Clyde  · 技术社区  · 8 年前

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

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

    假设我有一些这样的代码来表示这些,其中有一些有用的模块函数可以很好地处理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
    
    • t4代码生成似乎不是F项目的一个选项
    • 对于这种简单的模板化,类型提供程序似乎是多余的,而且据我所知,它们只能生成类,而不能生成模块。
    • 我知道Scott Wlaschin的 constrained strings 但是,在“创建一个类型”、“实现IwrappedString”、“创建一个公共构造函数”中,我得到了大致相同级别的重复代码。

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

    更新:

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

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

    而不是像

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


    Tomas的答案是,使用依赖类型的提供者nuget库是一个很好的方法,对于许多对其行为很好的人来说,这将是一个很好的解决方案。我觉得扩展和定制有点困难,除非我想维护我自己的类型提供者的副本,我希望避免这种情况。

    马塞洛提出的静态参数约束是一条相当有效的研究途径。它们基本上给了我想要的——一个基本上是静态方法的“接口”的通用参数。然而,更关键的是,它们需要内联函数来操作,我没有时间来评估在我的代码库中,这会有多重要。

    但我把它改为使用常规的通用约束。必须实例化一个对象以获得最大长度值有点愚蠢,fsharp type/generic代码只是粗俗的,但是从模块用户的角度来看,它是干净的,我可以很容易地根据自己的需要扩展它。

        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
    
    4 回复  |  直到 8 年前
        1
  •  1
  •   user4649737    8 年前

    你不能用吗 Static Parameters ,如F.数据包所示 HtmlProvider 例子?

        2
  •  3
  •   Tomas Petricek    8 年前

    我认为 BoundedString 从中键入提供程序 Dependent type provider Project使您能够准确地执行所需的操作。使用项目文档中的示例,可以执行以下操作:

    type ProductDescription = BoundedString<10, 2000>
    type ProductName = BoundedString<5, 50>
    
    type Product = { Name : ProductName; Description : ProductDescription }
    
    let newProduct (name : string) (description : string) : Product option =
      match ProductName.TryCreate(name), ProductDescription.TryCreate(description) with
      | Some n, Some d -> { Name = n; Description = d }
      | _ -> None
    

    我不知道有多少人在实践中使用这个项目,但它看起来很简单,而且它完全满足您的要求,所以它可能值得一试。

        3
  •  3
  •   Jwosty    8 年前

    使用一点仔细的反射魔法,我们可以取得很多,并得到一些真正不错的类型。像这样的怎么样?

    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"
    
        4
  •  2
  •   Aaron M. Eshbach    8 年前

    一个选项可能是对长度有限的字符串使用单个模块,该模块使用循环参数作为长度限制和字符串本身,然后部分应用限制参数。实现可能如下所示:

    module LimitedString =
        type T = private T of string
        let create length (s:string) = checkStringLength length s |> Result.map T
        let trustCreate length (s:string) = checkStringLength length s  |> Result.okVal |> T
        let truncateCreate length (s:string) = truncateStringToLength length s |> T
        let toString (T s) = s
    
        type T with
            member this.AsString = this |> toString
    

    然后,每个长度的模块仍然是必需的,但不会有所有样板文件:

    module String100 =
        let create = LimitedString.create 100
        let trustCreate = LimitedString.trustCreate 100
        let truncateCreate = LimitedString.truncateCreate 100
    

    编辑

    在阅读了评论和对原始帖子的更新之后,我会稍微改变一下我的建议。而不是定义 T 在每个模块中,对于顶层的每个字符串长度,我都有一个特定的结构类型的单大小写联合。然后,我会搬到 toString 到各个字符串模块。最后,我将向 LimitedString 模块允许我们部分应用长度和特定的单箱联合类型:

    [<Struct>] type String40 = private String40 of string
    [<Struct>] type String100 = private String100 of string
    
    module LimitedString =
        let create length ctor (s:string) = checkStringLength length s |> Result.map ctor
        let trustCreate length ctor (s:string) = checkStringLength length s  |> Result.okVal |> ctor
        let truncateCreate length ctor (s:string) = truncateStringToLength length s |> ctor
    
    module String40 =
        let create = LimitedString.create 40 String40
        let trustCreate = LimitedString.trustCreate 40 String40
        let truncateCreate = LimitedString.truncateCreate 40 String40
        let toString (String40 s) = s
    
    module String100 =
        let create = LimitedString.create 100 String100
        let trustCreate = LimitedString.trustCreate 100 String100
        let truncateCreate = LimitedString.truncateCreate 100 String100
        let toString (String100 s) = s
    
    type MyRecord =
        {
            FirstName: String40
            LastName: String100
        }
    

    这里仍然有相当多的样板文件,但我认为这是一个使用单案例联合和模块的解决方案的最佳方案。类型提供程序可能是可能的,但您必须考虑添加的复杂性是否超过样板文件。