Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculating a running mean of a generic type

I'm trying to do something that would be easy if I could use type classes in F#.

I want to be able to calculate a running average on an arbitrary type upon which I have defined addition, scalar multiplication and scalar division. This is my non-compiling attempt so far:

let updateMean<'a when 'a : (static member (*) : 'a -> float32 -> 'a) and 'a : (static member (/) : 'a -> float32 -> 'a) and 'a : (static member (+) : 'a -> 'a -> 'a)> (newObservation : 'a) (currentMean : 'a) (currentNumberOfRecords : int) =
    (newObservation + (currentMean * (currentNumberOfRecords |> float32))) / float32 (1 + currentNumberOfRecords)

and this is the confusing error message:

A type parameter is missing a constraint 'when ( ^a or  ^?766914) : (static member ( + ) :  ^a *  ^?766914 ->  ^?766915)'
like image 330
Rob Lyndon Avatar asked Feb 02 '14 19:02

Rob Lyndon


1 Answers

You can get a bit further if you add inline (which is a requirement for using static member constraints) and rename the static variable from 'a to ^a (which is a syntax used by statically resolved type parameters) and remove the explicit specification of constraints. The compiler will then attempt to infer the type of the constraints based on the code (which makes it a bit more usable):

let inline updateMean (newObservation : ^a) (currentMean : ^a) 
                      (currentNumberOfRecords : int) =
    (newObservation + (currentMean * (currentNumberOfRecords |> float32))) / 
       (float32 (1 + currentNumberOfRecords))

However, this still does not work, because you are restricting currentMean to float32 - in general, the compiler requires that both of the parameters of an operator have the same type. You can keep the currentNumberOfRecords as a value of the same type - then the only tricky part is adding one, which can be done using LanguagePrimitives.GenericOne:

let inline updateMean newObservation currentMean currentNumberOfRecords =
    (newObservation + (currentMean * currentNumberOfRecords)) / 
      (LanguagePrimitives.GenericOne + currentNumberOfRecords)

This works nicely, but I would probably use a slightly different approach and keep the total sum together with the total count (and then just divide them to get the mean - this will probably have better numerical properties as you avoid repeated rounding):

let inline updateState (currentSum, currentCount) newObservation = 
  (currentSum + newObservation, currentCount + 1)

let inline currentMean (currentSum, currentCount) = 
  LanguagePrimitives.DivideByInt currentSum currentCount

The trick is to use operations from the LanguagePrimitives module and let the F# compiler figure out the type constraints automatically (because they are pretty ugly).

like image 151
Tomas Petricek Avatar answered Sep 28 '22 21:09

Tomas Petricek