F # Активный шаблон List.filter или эквивалент

У меня есть записи типов

type tradeLeg = {
    id : int ;
    tradeId : int ;
    legActivity : LegActivityType ;
    actedOn : DateTime ;
    estimates : legComponents ;
    entryType : ShareOrDollarBased ;
    confirmedPrice: DollarsPerShare option;
    actuals : legComponents option ; 


type trade = {
    id : int ;
    securityId : int ;
    ricCode : string ;
    tradeActivity : TradeType ;
    enteredOn : DateTime ;
    closedOn : DateTime ;
    tradeLegs : tradeLeg list  ;
}

Очевидно, что tradeLegs - это тип сделки. Нога может быть решена или неурегулирована (или неурегулирована, но подтверждена цена) - таким образом, я определил активный шаблон:

let (|LegIsSettled|LegIsConfirmed|LegIsUnsettled|) (l: tradeLeg) = 
        if Helper.exists l.actuals then LegIsSettled
        elif Helper.exists l.confirmedPrice then LegIsConfirmed
        else LegIsUnsettled

а затем определить, разрешена ли сделка (на основе всех ног, соответствующих шаблону LegIsSettled:

let (|TradeIsSettled|TradeIsUnsettled|) (t: trade) = 
        if List.exists (
            fun l -> 
                match l with 
                    | LegIsSettled -> false 
                    | _ -> true) t.tradeLegs then TradeIsSettled
        else TradeIsUnsettled

Я могу видеть некоторые преимущества этого использования активных шаблонов, однако я бы подумал, что есть более эффективный способ увидеть, соответствует ли какой-либо элемент списка (или не соответствует) шаблону actie без необходимости писать лямбда выражение специально для него и использование List.exist.

Вопрос два раза:

  • есть ли более сжатый способ выразить это?
  • Есть ли способ абстрагировать функциональность/выражение

    (fun l -> 
          match l with 
          | LegIsSettled -> false 
          | _ -> true)
    

Таким образом,

let itemMatchesPattern pattern item  =
    match item with
         | pattern -> true
         | _ -> false

такой я мог бы написать (поскольку я повторно использую этот шаблон дизайна):

let curriedItemMatchesPattern = itemMatchesPattern LegIsSettled
if List.exists curriedItemMatchesPattern t.tradeLegs then TradeIsSettled
        else TradeIsUnsettled

Мысли?

Ответ 1

Чтобы ответить на ваш вопрос об активных шаблонах, позвольте мне использовать более простой пример:

let (|Odd|Even|) n = 
  if n % 2 = 0 then Even else Odd

Когда вы объявляете шаблон с несколькими параметрами с помощью (|Odd|Even|), компилятор понимает его как функцию, возвращающую значение типа Choice<unit, unit>. Таким образом, активным шаблоном, с которым вы можете работать, является целая комбинация |Odd|Even|, а не только две конструкции, которые вы можете использовать независимо (например, |Odd| и |Even|).

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

let pattern = (| Odd | Even |);;    val pattern: int → Choice

Вы можете написать функцию, которая проверяет, соответствует ли значение указанному шаблону, но вам нужно много функций (потому что существует много типов Choice, перегруженных количеством параметров типа):

let is1Of2 pattern item = 
  match pattern item with
  | Choice1Of2 _ -> true
  | _ -> false

> is1Of2 (|Odd|Even|) 1  
val it : true

Что-то вроде этого будет работать в вашем случае, но оно далеко не идеальное.

Вы можете сделать немного лучшую работу, если вы объявите несколько частичных активных шаблонов (но тогда вы, конечно, потеряете некоторые приятные аспекты всех активных шаблонов, таких как проверка полноты):

let (|Odd|_|) n = 
  if n % 2 = 0 then None else Some()  
let (|Even|_|) n = 
  if n % 2 = 0 then Some() else None

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

let matches pattern value = 
  match pattern value with
  | Some _ -> true
  | None -> false

> matches (|Odd|_|) 1;;
val it : bool = true
> matches (|Even|_|) 2;;
val it : bool = true

Резюме. Хотя может быть какой-то более или менее элегантный способ достичь того, что вам нужно, я бы, вероятно, подумал, что активные шаблоны дают вам какое-либо большое преимущество перед использованием стандартных функций. Возможно, лучше подумать о том, как использовать код сначала с помощью функций, а затем решить, какая из конструкций будет полезна в качестве активных шаблонов и позже добавит активные шаблоны. В этом случае обычный код выглядел бы намного хуже:

type LegResult = LegIsSettled | LegIsConfirmed | LegIsUnsettled

let getLegStatus (l: tradeLeg) =    
    if Helper.exists l.actuals then LegIsSettled   
    elif Helper.exists l.confirmedPrice then LegIsConfirmed   
    else LegIsUnsettled

// Later in the code you would use pattern matching
match getLegStatus trade with
| LegIsSettled -> // ...
| LegIsUnSettled -> // ...

// But you can still use higher-order functions too
trades |> List.exist (fun t -> getLegStatus t = LegIsSettled)

// Which can be rewritten (if you like point-free style):
trades |> List.exist (getLegStatus >> ((=) LegIsSettled))

// Or you can write helper function (which is more readable):
let legStatusIs check trade = getLegStatus trade = check
trades |> List.exist (legStatusIs LegIsSettled)

Ответ 2

В дополнение к точкам Томаса о фактических деталях активных паттернов, обратите внимание, что вы всегда можете сократить fun x -> match x with |... до function | ..., что позволит сэкономить несколько нажатий клавиш, а также необходимость создания потенциально бессмысленного идентификатора.