Appropriate use of active templates in F #
I am using active template to parse usage events in csv format usage log. The active part of the template is shown below. Parsing the entire file works well and the generated sequence is filled with all kinds of UsageEvents.
type SystemName = string
type SystemVersion = string
type MAC = string
type Category = string
type Game = string
type Setting = string
type StartupLocation = string
type UsageEvent =
| SystemStart of DateTime * SystemVersion * SystemName * MAC
| SystemEnd of DateTime
| GameStart of DateTime * Category * Game * Setting * StartupLocation
| GameEnd of DateTime * Category * Game
| Other
let (|SystemStart|SystemEnd|GameStart|GameEnd|Other|) (input : string list) =
match List.nth input 0 with
| "SystemStartedEvent" ->
SystemStart (DateTime.Parse (List.nth input 1), List.nth input 2, List.nth input 3, List.nth input 4)
| "SystemEndedEvent" ->
SystemEnd (DateTime.Parse (List.nth input 1))
| "GameStartedEvent" ->
GameStart (DateTime.Parse (List.nth input 1), List.nth input 2, List.nth input 3, List.nth input 4, List.nth input 5)
| "GameEndedEvent" ->
GameEnd (DateTime.Parse (List.nth input 1), List.nth input 2, List.nth input 3)
| _ ->
Other
The problem is that I am probably using ActivePattern incorrectly. I would like to traverse the list in order to create a tree from it based on some logic, but I have no way of matching the entry in the sequence after parsing.
let CountSystemStart (entries : UsageEvent list) =
let rec loop sum = function
| SystemStart(_,_,_,_) -> sum + 1
| _ -> sum
loop 0 entries
This match doesn't work because the loop function requires string list
. What other way can I use the data contained in the unions, or should I match the input and then store it in a normal type?
source to share
To add to @Petr's answers - UsageEvent
and your templates with active templates have the same names, so the active template that is defined later is a shadow union type. What comes from string list
is likely.
I would just completely remove the active template and add the function Parse
(or rather ParseParts
, since you want to pass it a list of strings) in UsageEvent
.
type UsageEvent =
| SystemStart of DateTime * SystemVersion * SystemName * MAC
| (...and so on...)
static member ParseParts (input: string list) =
match input with
| ["SystemStartedEvent"; date; version; name; mac] ->
SystemStart (DateTime.Parse date, version, name, mac)
| (...and so on...)
Active templates are cute, but you really need a good script to make them shine. Otherwise, if you can avoid using a simple function, just use a regular function.
source to share
There are 2 problems in this code:
-
Discriminatory union
UsageEvent
and active pattern selection functions have the same names -
A recursive loop function is not recursive because it is written - it does not call itself.
When you map in the UsageEvent list, try using the fully qualified type name.
I would rewrite your CountSystemStart function as:
let CountSystemStart (entries : UsageEvent list) =
let rec loop sum = function
| [] -> sum
| (UsageEvent.SystemStart(_))::rest -> loop (sum + 1) rest
| _::rest -> loop sum rest
loop 0 entries
source to share