РЕДАКТИРОВАТЬ: возможные решения внизу
Я делаю некоторую работу с данными, где мне нужно быть очень осторожным с длинами строк, которые в конечном итоге будут отправлены в текстовом выводе фиксированной ширины, хранящемся в полях 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
- Генерация кода T4, кажется, не вариант для проектов F #
- Поставщики типов кажутся излишними для такого родапростые шаблоны и, насколько я могу судить, они могут создавать только классы, а не модули.
- Мне известна страница ограниченных строк Скотта Влашина *1019*, но в итоге получается примерно то же самоеуровень повторяющегося кода в шагах «Создание типа», «Реализация IWrappedString», «создание открытого конструктора», которые он перечисляет.
Эти кодовые блоки довольно короткие, и это не был бы конец света, чтобы просто скопировать / вставить дюжину раз для различных длин полей.Но я чувствую, что мне не хватает более простого способа сделать это.
ОБНОВЛЕНИЕ:
Еще одно замечание: важно, чтобы записи, использующие эти типы, давали информацию о том, какого типа они несут.:
type MyRecord =
{
FirstName: String40;
LastName: String100;
}
и не быть чем-то вроде
type MyRecord =
{
FirstName: LimitedString;
LastName: LimitedString;
}
Ответ Томаса, использующий библиотеку nuget Depended Type Provider, является довольно хорошим и был бы хорошим решением для многих людей, которые довольны его поведением как есть.Я чувствовал, что было бы немного сложно расширять и настраивать, если бы я не хотел поддерживать свою собственную копию поставщика типов, которую я надеялся избежать.
Предложение Марсело относительно ограничений статических параметров было довольно продуктивным путемисследование.Они дают мне в основном то, что я искал - общий аргумент, который в основном является «интерфейсом» для статических методов.Однако главное, что им требуются встроенные функции, и у меня нет времени, чтобы оценить, сколько это будет иметь или не иметь значения в моей кодовой базе.
Но я взял это и изменил его, чтобы использовать обычныеобщие ограничения.Немного глупо создавать экземпляр объекта, чтобы получить значение максимальной длины, а тип / общий код 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