6

What is the best way to write your functions so they can handle seamlessly:

  • a float seq
  • a timestamped series of the type { ts : DateTime; value: float } seq, where the values are in the float called value

I need in particular to write functions such as computing the average/variance/random transformations of the time series, and I would like to write only 1 version of these functions.

BlueTrin
  • 9,610
  • 12
  • 49
  • 78
  • 2
    What operations do you want to execute on them? What do you mean by *generic manner*? `float` is a particular type, not a generic type. – pad Oct 24 '12 at 09:18
  • @pad: you are right let me rewrite the question – BlueTrin Oct 24 '12 at 09:23

2 Answers2

14

As asked in a comment, the key question is what kind of operations do you want to write over the data type. It might be possible to do this for basic numerical operations, but if you need some more complex computations, then it will probably be easier to write two separate functions.

Anyway, if you want to use basic numerical operations, then you need to define standard numeric operators for your time stamped type. I described this in a recent article. The following implements +, division by integer and zero:

open System

type TimedFloat = 
  { Time : DateTime
    Value : float }
  static member (+) (tf1, tf2) = 
    { Time = DateTime(tf1.Time.Ticks + tf2.Time.Ticks)
      Value = tf1.Value + tf2.Value }
  static member Zero = 
    { Time = DateTime.MinValue
      Value = 0.0 } 
  static member DivideByInt(tf, n) =
    { Time = DateTime(tf.Time.Ticks / int64 n)
      Value = tf.Value / float n }

The + operator is a bit suspicious, because you'll end up with some very large date (and perhaps using TimeSpan from the last would make more sense). However, once you define the operators, you can, for example, use Seq.average:

[ { Time = DateTime.Now
    Value = 3.0 }
  { Time = DateTime.Now.AddDays(2.0)
    Value = 10.0 } ]
|> Seq.average

The Seq.average function works on both types because it is written using static member constraints (meaning that it works on any type that has necessary members). Writing such functions is more difficult than writing normal function, so I would probably not use this style by default. Anyway, here is an introduction with more examples and this SO answer shows more useful tricks.

EDIT - As Jon Harrop points out, this is quite complex approach and it gives you only limited benefits. If you just need to work with the values then converting to a sequence of values is a better approach. If you need more complex computations, then I don't think it is worth writing a generic function. Writing two separate functions for float and timestamped values will probably be easier.

Community
  • 1
  • 1
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thanks for this answer. I've been wondering about this kind of thing because I come to F# from Haskell, where I'd just look for a suitable typeclass (or define my own) to encompass all the necessary types for a polymorphic function. If there isn't a good concept for such a typeclass, then I'd write two functions. I keep forgetting that F# engages in OO-world concepts like the overloading of individual operators. – Matthew Walton Oct 24 '12 at 09:41
  • -1 Sorry but that just looks like a really convoluted and inefficient alternative to just mapping over the timestamped sequence and extracting the `value` fields before feeding the resulting `float seq` into whatever function you're applying it to. – J D Oct 24 '12 at 09:46
  • from the question I cannot tell if the OP wants to aggregate over the timestamps as well or not - so this answer is sureley not wrong - so the -1 is IMHO unnecessary - gave both your answers a +1 – Random Dev Oct 24 '12 at 10:42
  • @JonHarrop You're right. And I also said I would not probably use this style, but it is answer to the question. But I agree that I should have been more clear. – Tomas Petricek Oct 24 '12 at 10:45
  • 1
    I gave +1 to both questions because they answered different needs and I was unclear in my question. In my case I ended up using Jon's solution, but I appreciate your answer as well. – BlueTrin Oct 24 '12 at 13:09
9

Just convert the timestamped seq into a float seq like this:

xs
|> Seq.map (fun x -> x.value)
J D
  • 48,105
  • 13
  • 171
  • 274
  • 3
    If you only need to calculate with values, then this is the way to go. But obviously, it does not work if you need to consider the timestamps (e.g. to interpolate the values you need the time too). That's why my answer shows a more complex approach. – Tomas Petricek Oct 24 '12 at 10:46