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:
- is there a more concise way to express this?
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 coul开发者_如何学JAVAd write (as I am reusing this design-pattern):
let curriedItemMatchesPattern = itemMatchesPattern LegIsSettled
if List.exists curriedItemMatchesPattern t.tradeLegs then TradeIsSettled
else TradeIsUnsettled
Thoughts?
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)
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.
精彩评论