anchor.diff:  exception ParseError of string
   
   let parseError s = raise (ParseError s)
  -let isNumeric (s:string) = Decimal.TryParse(s) |> fst
  +
  +type InferredType =   
  +  | Any | String | Number 
  +  | OneZero | Bool 
  +  | Date of System.Globalization.CultureInfo
  +
  +let tryDate culture s = 
  +  DateTimeOffset.TryParse(s, culture, Globalization.DateTimeStyles.AssumeUniversal) |> fst
  +
  +let mmdd = System.Globalization.CultureInfo.InvariantCulture 
  +let ddmm = System.Globalization.CultureInfo.GetCultureInfo("en-GB")
  +
  +let inferType (s:string) =
  +  if fst (Decimal.TryParse(s)) then 
  +    let d = Decimal.Parse(s)
  +    if d = 1M || d = 0M then OneZero
  +    else Number
  +  elif tryDate mmdd s && tryDate ddmm s then Date null
  +  elif tryDate mmdd s then Date mmdd
  +  elif tryDate ddmm s then Date ddmm
  +  elif s.ToLower() = "true" || s.ToLower() = "false" then Bool
  +  else String
  +
  +let typeName = function
  +  | String | Any -> "string"
  +  | OneZero | Bool -> "bool"
  +  | Date _ -> "date"
  +  | Number -> "number"
  +
  +let unifyTypes t1 t2 = 
  +  match t1, t2 with
  +  | Any, t | t, Any -> t
  +  | Date c, Date null | Date null, Date c -> Date c
  +  | Date c1, Date c2 when c1 = c2 -> Date c1
  +  | Number, Number -> Number
  +  | Bool, OneZero | OneZero, Bool -> Bool
  +  | Number, OneZero | OneZero, Number -> Number
  +  | _ -> String
   
   let readCsvFile (data:string) =   
     if String.IsNullOrWhiteSpace data then parseError "The specified input was empty."
  
positive.code: let inline pickField name obj = 
    Array.pick (fun (n, v) -> if n = name then Some v else None) obj
  
  let inline the s = match List.ofSeq s with [v] -> v | _ -> failwith "Not unique"
  let asString = function String s -> s | Number n -> string n
  let asDecimal = function String s -> decimal s | Number n -> n
  
  let applyAggregation kvals group = function
   | GroupKey -> kvals
   | CountAll -> [ "count", Number(group |> Seq.length |> decimal) ]
   | CountDistinct(fld) -> [ fld, Number(group |> Seq.distinctBy (pickField fld) |> Seq.length |> decimal) ]
   | ReturnUnique(fld) -> [ fld, group |> Seq.map (pickField fld) |> the ]
   | ConcatValues(fld) -> [ fld, group |> Seq.map(fun obj -> pickField fld obj |> asString) |> Seq.distinct |> String.concat ", " |> String ]
   | Sum(fld) -> [ fld, group |> Seq.sumBy (fun obj -> pickField fld obj |> asDecimal) |> Number ]
   | Mean(fld) -> [ fld, group |> Seq.averageBy (fun obj -> pickField fld obj |> asDecimal) |> Number ]
  
  let compareFields o1 o2 (fld, order) = 
    let reverse = if order = Descending then -1 else 1
  
negative.code:     else ","
    let file = try CsvFile.Parse(data,separators) with _ -> parseError "Failed to parse data as a CSV file."
    if Seq.isEmpty file.Rows then parseError "The specified CSV file contains no data."
  
    let meta = 
      file.Rows 
      |> Seq.truncate 100
      |> Seq.map (fun r -> Array.map inferType r.Columns)
      |> fun x -> printfn "%A" x; x
      |> Seq.reduce (Array.map2 unifyTypes)
      |> Seq.zip file.Headers.Value
      |> Array.ofSeq
    
  