f# - 如何压缩这个重复的 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
- T4 代码生成似乎不是 F# 项目的选项
- 对于这种简单的模板,类型提供者似乎有点过头了,而且据我所知,它们只能生成类,而不是模块。
- 我知道 Scott Wlaschin 的约束字符串页面,但在他列出的“创建类型”、“实现 IWrappedString”、“创建公共构造函数”步骤中,我最终得到了大致相同级别的重复代码。
这些代码块相当短,对于不同的字段长度只需复制/粘贴十几次就不会是世界末日。但我觉得我错过了一种更简单的方法来做到这一点。
更新:
另一个注意事项是,使用这些类型的记录提供有关它们所携带的类型的信息很重要:
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
解决方案
使用一点仔细的反射魔法,我们可以实现很多并获得一些非常好的类型。这样的事情怎么样?
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"
推荐阅读
- c# - 将列添加到 CSV 并将列合并到此新列
- php - Ajax SyntaxError:JSON 输入意外结束
- java - 在 CDI 中生成可选 Bean
- kubernetes - prometheus 警报规则和配置 ui 工具?
- azure - 图形 API 的编程身份验证
- powerbi - PowerBI - 将 DatePicker 切片器中的日期格式设置为 dd/mm/yyyy
- android - 如何为列表视图中的每个项目使用相同的图像
- service-fabric-stateful - 构建 Service Fabric 应用程序需要大量时间
- java - Apache NetBeans 11.1 无法正常工作
- javascript - 从 Firebase 数据库中检索过去 2 天的结果