Вычисление скользящего среднего для универсального типа
Я пытаюсь сделать что-то, что было бы легко, если бы я мог использовать классы типов в 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# автоматически определять ограничения типов (потому что они довольно уродливы).