Как мне сжать этот повторяющийся код F #? - PullRequest
0 голосов
/ 21 мая 2018

РЕДАКТИРОВАТЬ: возможные решения внизу

Я делаю некоторую работу с данными, где мне нужно быть очень осторожным с длинами строк, которые в конечном итоге будут отправлены в текстовом выводе фиксированной ширины, хранящемся в полях 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

Ответы [ 4 ]

0 голосов
/ 22 мая 2018

Используя прикосновение магии тщательного отражения, мы можем многого добиться и получить действительно классные типы.Как насчет этого?

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"
0 голосов
/ 21 мая 2018

Разве вы не можете использовать Статические параметры , как показано на примере HtmlProvider пакета F # .Data?

0 голосов
/ 21 мая 2018

Я думаю, что провайдер типа 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

Я не знаю, сколько людей используют этот проект на практике, но он кажется довольно простым и делает именно то, что вы просилитак что, возможно, стоит попробовать.

0 голосов
/ 21 мая 2018

Одним из вариантов может быть использование одного модуля для строк ограниченной длины, который использует параметры карри для ограничения длины и самой строки, а затем только частично применить параметр 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 внутри каждого модуля, я бы имел конкретное единственное в своем роде объединение типа структуры для каждой длины строки на верхнем уровне.Затем я бы перешел к 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
    }

Здесь все еще довольно много шаблонов,но я думаю, что это является приблизительным решением для решения с использованием единичных объединений и модулей.Поставщик типов может быть возможен, но вам следует подумать, перевесит ли дополнительная сложность шаблонный шаблон.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...