Вычисление скользящего среднего для универсального типа

Я пытаюсь сделать что-то, что было бы легко, если бы я мог использовать классы типов в F#.

Я хочу иметь возможность вычислить скользящее среднее для произвольного типа, для которого я определил сложение, скалярное умножение и скалярное деление. Это моя некомпилированная попытка:

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)

и это сбивающее с толку сообщение об ошибке:

A type parameter is missing a constraint 'when ( ^a or  ^?766914) : (static member ( + ) :  ^a *  ^?766914 ->  ^?766915)'

1 ответ

Решение

Вы можете получить немного дальше, если вы добавите inline (что является обязательным условием для использования статических ограничений членов) и переименуйте статическую переменную из 'a в ^a (это синтаксис, используемый статически разрешенными параметрами типа) и удалить явную спецификацию ограничений. Затем компилятор попытается определить тип ограничений на основе кода (что делает его немного более удобным):

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

Тем не менее, это все еще не работает, потому что вы ограничиваете currentMean в float32 - в общем, компилятор требует, чтобы оба параметра оператора имели одинаковый тип. Вы можете сохранить currentNumberOfRecords в качестве значения того же типа - тогда единственная сложная часть - это добавить единицу, что можно сделать, используя LanguagePrimitives.GenericOne:

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

Это хорошо работает, но я бы, вероятно, использовал немного другой подход и сохранил бы общую сумму вместе с общим количеством (а затем просто разделил их, чтобы получить среднее значение - это, вероятно, будет иметь лучшие числовые свойства, так как вы избежите повторного округления):

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

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

Хитрость заключается в том, чтобы использовать операции из модуля LanguagePrimitives и позволить компилятору F# автоматически определять ограничения типов (потому что они довольно уродливы).

Другие вопросы по тегам