编辑:底部的可能解决方案
我正在做一些数据工作,我需要非常小心字符串长度,最终将在固定宽度的文本输出中发送,存储在有限大小的nvarchar字段等中。我希望有一个很好的严格输入这些而不是裸体的System.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;
}
<小时/> 托马斯&#39;回答,使用Depended Type Provider nuget库是一个非常好的解决方案,并且对于许多对其行为一直很好的人来说都是一个很好的解决方案。我觉得扩展和自定义会有点棘手,除非我想维护我自己希望避免的类型提供者的副本。
马塞洛关于静态参数约束的建议是一条相当富有成效的研究路径。他们基本上给了我我想要的东西 - 一个基本上是一个界面的通用论点&#39;对于静态方法。然而,问题是它们需要内联函数才能运行,而我没有时间来评估代码库中有多少重要或不重要。
但是我接受了并改变它以使用常规的通用约束。有必要实例化一个对象以获得最大长度值有点傻,而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
答案 0 :(得分:3)
我认为Dependent type provider项目中的BoundedString
类型提供程序可让您完全满足您的需求。使用项目文档中的示例,您可以执行以下操作:
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
我不知道有多少人在实践中使用这个项目,但它看起来很简单,它完全符合你的要求,所以值得一试。
答案 1 :(得分:3)
使用一丝细致的反射魔法,我们可以实现很多并获得一些非常好的类型。这样的事情怎么样?
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"
答案 2 :(得分:2)
一个选项可能是将单个模块用于有限长度的字符串,该字符串使用curried参数作为长度限制和字符串本身,然后只部分应用limit参数。实现可能如下所示:
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
类型,而是在顶层为每个字符串长度设置一个特定的struct-type单案例并集。然后,我将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
}
这里仍有相当数量的样板,但我认为这是使用单箱联合和模块的解决方案。类型提供程序可能是可能的,但您必须考虑增加的复杂性是否超过样板。
答案 3 :(得分:1)
你不能使用Static Parameters,如F#.Data包的HtmlProvider示例所示?