Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# Active Pattern List.filter or equivalent

I have a records of types

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  ;
}

Obviously the tradeLegs are a type off of a trade. A leg may be settled or unsettled (or unsettled but price confirmed) - thus I have defined the active pattern:

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

and then to determine if a trade is settled (based on all legs matching LegIsSettled pattern:

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

I can see some advantages of this use of active patterns, however i would think there is a more efficient way to see if any item of a list either matches (or doesn't) an actie pattern without having to write a lambda expression specifically for it, and using List.exist.

Question is two fold:

  1. is there a more concise way to express this?
  2. is there a way to abstract the functionality / expression

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

Such that

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

such I could write (as I am reusing this design-pattern):

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

Thoughts?

like image 897
akaphenom Avatar asked Dec 22 '22 03:12

akaphenom


2 Answers

To answer your question about active patterns, let me use a simpler example:

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

When you declare a pattern that has multiple options using (|Odd|Even|), then the compiler understands it as a function that returns a value of type Choice<unit, unit>. So, the active pattern that you can work with is the whole combination |Odd|Even| and not just two constructs that you could use independently (such as |Odd| and |Even|).

It is possible to treat active patterns as first class functions, but if you're using patterns with multiple options, you cannot do much with it:

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

You can write function that tests whether a value matches a specified pattern, but you'd need a lot of functions (because there are many Choice types overloaded by the number of type parameters):

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

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

Something like this would work in your case, but it is far from being perfect.

You can do a little better job if you declare multiple partial active patterns (but then you of course loose some nice aspects of full active patterns such as completeness checking):

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

Now you can write a function that checks whether a value matches pattern:

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

Summary While there may be some more or less elegant way to achieve what you need, I'd probably consider whether active patterns give you any big advantage over using standard functions. It may be a better idea to implenent the code using functions first and then decide which of the constructs would be useful as active patterns and add active patterns later. In this case, the usual code wouldn't look much worse:

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)
like image 124
Tomas Petricek Avatar answered Jan 07 '23 13:01

Tomas Petricek


In addition to Tomas's points on the actual details of active patterns, note that you can always shorten fun x -> match x with |... to function | ..., which will save a few keystrokes as well as the need to make up a potentially meaningless identifier.

like image 31
kvb Avatar answered Jan 07 '23 15:01

kvb