Выдача исключения при создании записи F # с недопустимыми значениями - PullRequest
0 голосов
/ 01 февраля 2019

Я новичок в F #, так что извините, если это вопрос новичка.

Я работаю над курсом Владимира Хорикова "Многоплановая работа" "Практическое проектирование на основе предметной области".Его примеры реализованы с использованием C #, поэтому на практике я пытаюсь реализовать их в F #.

У него есть класс "Money", который в F # выглядит следующим образом:

 type Money =
    {
        OneCentCount: int;
        TenCentCount: int;
        QuarterCount: int;
        OneDollarCount: int;
        FiveDollarCount: int;
        TwentyDollarCount: int;
    } 

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

let money1 = Money(1,2,3,4,5,6)

выдает ошибку, что для Money нет конструктора. Поэтому я должен сделать

let money1 = Money { 
    OneCentCount = 1; 
    TenCentCount = 2; 
    QuarterCount = 3; 
    OneDollarCount = 4; 
    FiveDollarCount = 5; 
    TwentyDollarCount = 6}

Теперь, однако,он переходит к тестированию, и ему нужен тест, который выдает исключение InvalidOp, если вы пытаетесь создать запись Money с любым из отрицательных значений - разумное требование.

Но поскольку нет конструктора для типа MoneyЯ не могу понять, куда поместить код для проверки на недопустимые значения и выдать исключение.

Может кто-нибудь дать мне несколько советов? Спасибо.

Ответы [ 4 ]

0 голосов
/ 02 февраля 2019

Подход, который я обычно использую для DDD в F #, заключается в создании типа для каждой отдельной комбинации бизнес-правил, которые я хочу применить:

[<Struct>] type MonetaryUnitCount = private MonetaryUnitCount of int

Эти типы имеют частные конструкторы,таким образом, мы можем контролировать, как они создаются, предоставляя только одну функцию для создания каждого типа:

module MonetaryUnitCount =
    let create count =
        if count < 0
        then Error "Count must be positive"
        else Ok (MonetaryUnitCount count)

Тогда каждый тип записи также будет иметь закрытый конструктор и соответствующую функцию create, которая вызывает правильныйcreate функция для каждого поля, проверка данных по мере их поступления:

type Money =
    private {
        OneCentCount: MonetaryUnitCount 
        TenCentCount: MonetaryUnitCount 
        QuarterCount: MonetaryUnitCount 
        OneDollarCount: MonetaryUnitCount 
        FiveDollarCount: MonetaryUnitCount 
        TwentyDollarCount: MonetaryUnitCount 
    } 

module Money =
    let create (a, b, c, d, e, f) =
        MonetaryUnitCount.create a
        |> Result.bind (fun m -> MonetaryUnitCount.create b |> Result.map (fun n -> m, n))
        |> Result.bind (fun (m, n) -> MonetaryUnitCount.create c |> Result.map (fun o -> m, n, o))
        |> Result.bind (fun (m, n, o) -> MonetaryUnitCount.create d |> Result.map (fun p -> m, n, o, p))
        |> Result.bind (fun (m, n, o, p) -> MonetaryUnitCount.create e |> Result.map (fun q -> m, n, o, p, q))
        |> Result.bind (fun (m, n, o, p, q) -> MonetaryUnitCount.create f |> Result.map (fun r -> m, n, o, p, q, r))
        |> Result.map (fun (m, n, o, p, q, r) -> 
            {
                OneCentCount = m
                TenCentCount = n
                QuarterCount = o
                OneDollarCount = p
                FiveDollarCount = q
                TwentyDollarCount = r
            })

Таким образом, вы либо получите успешный результат с заполненным Money, либо ошибку с ошибкой проверки.Нет способа создать экземпляр MonetaryUnitCount с отрицательным значением, и нет способа создать экземпляр Money с недопустимым MonetaryUnitCount, поэтому любой существующий экземпляр должен быть действительным.

Этот синтаксис может быть значительно упрощен при использовании Вычислительного выражения для автоматической привязки типа Result или улучшен при использовании аппликативов для сбора всех ошибок проверки.

0 голосов
/ 01 февраля 2019

Типичным подходом было бы иметь умный конструктор для типа, определенного как статический член (пример для упрощенного типа):

type Money =
    {
        OneCentCount: int;
        TenCentCount: int;
    }
    static member Create (oneCent, tenCent) =
        let throwOnNegative field v =
            if v < 0 then invalidOp (sprintf "Negative value for %s" field) else v
        {
            OneCentCount = oneCent |> throwOnNegative "OneCentCount"
            TenCentCount = tenCent |> throwOnNegative "TenCentCount"
        }

Любая логика проверки может входить в тело Createфункция.

0 голосов
/ 02 февраля 2019

Существующие два ответа дают хорошую иллюстрацию основной идеи - вам необходимо скрыть некоторые внутренние элементы типа данных и предоставить настраиваемую операцию для создания значений, реализующих проверки.

Одна вещь, котораяв вашем примере может быть полезно отделить проверку от типа Money - следуя подходу в существующих ответах, вы должны повторить проверку для каждого отдельного поля, что довольно утомительно.Кроме того, вы можете определить тип Count, который использует ту же технику сокрытия, чтобы разрешить только положительные значения, а затем определить вашу запись в терминах Count:

type Count = 
  private { Count : int }
  member x.Value = x.Count

let Count n = 
  if n < 0 then invalidOp "Negative count!"
  else { Count = n }

Теперь вы можете использовать только обычную запись:

type Money =
  { OneCentCount: Count
    TenCentCount: Count
    QuarterCount: Count
    OneDollarCount: Count
    FiveDollarCount: Count
    TwentyDollarCount: Count } 

При создании значения записи она работает как обычная запись, но вы должны создать все значения Count, используя функцию Count, которая выполняет проверки:

let money = 
  { OneCentCount = Count 10
    TenCentCount = Count 10
    QuarterCount = Count -1
    OneDollarCount = Count 10
    FiveDollarCount = Count 10
    TwentyDollarCount = Count 10 } 
0 голосов
/ 01 февраля 2019

Один трюк, который вы можете сделать, это теневое копирование:

type Money =
    {
        OneCentCount: int;
        TenCentCount: int;
        QuarterCount: int;
        OneDollarCount: int;
        FiveDollarCount: int;
        TwentyDollarCount: int;
    } 

 let Money (a, b, c, d, e, f) =  
   { OneCentCount = a; 
    TenCentCount = b; 
    QuarterCount = c; 
    OneDollarCount = d; 
    FiveDollarCount = e; 
    TwentyDollarCount = f}

Затем в вашей функции Money(a,b,c,d,e,f) вы можете поместить соответствующую логику проверки.Например, мы не можем разрешить отрицательные значения для OneCentCount:

let Money (a, b, c, d, e, f) =  
    if a < 0 then
        raise(invalidArg("a is required to be a positive value"))
    else
        {  OneCentCount = a; 
            TenCentCount = b; 
            QuarterCount = c; 
            OneDollarCount = d; 
            FiveDollarCount = e; 
            TwentyDollarCount = f}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...