Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Average of collection functions should support more numeric types #812

Open
mvkra opened this issue Nov 28, 2019 · 3 comments
Open

Average of collection functions should support more numeric types #812

mvkra opened this issue Nov 28, 2019 · 3 comments

Comments

@mvkra
Copy link

@mvkra mvkra commented Nov 28, 2019

Average of collection functions should support more numeric types

I propose we allow Seq.average, Array.average and friends to work on a wider variety of numeric data types (e.g int, int64, etc.). Currently only data types with a static member of "DivideByInt" are supported.

Pros and Cons

The advantages of making this adjustment to F# are:

  • Calculating an average of a collection is more intuitive to a newcomer of the language (i.e. it just works as expected for any number type).
  • More efficient performance wise than converting your numbers to another data type before calculating the average.

The disadvantages of making this adjustment to F# are:

  • It changes the type constraints of functions like Seq.average and may break consumers.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S

Notes

The existing way of approaching this problem in F# would be to hand roll your own function to do this. e.g.

let inline average s = 
  let mutable sum = LanguagePrimitives.GenericZero
  let mutable items = LanguagePrimitives.GenericZero
  for i in s do
    sum <- sum + i
    items <- items + LanguagePrimitives.GenericOne
  sum / items

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this
@gusty

This comment has been minimized.

Copy link

@gusty gusty commented Nov 28, 2019

It's not clear to me what is the signature of average you are suggesting. It's also not obvious which one should be.

Your sample function gives you a signature of s:seq< ^a> -> ^e but in fact ^a and ^e are the same type when they are resolved.

I can think of 2 possibilities:

  1. s:seq< ^a> -> ^a in which case if you work with big numbers you'll be able to get a big number as result. But if you work with small numbers and you have a big number of elements your divisor won't be able to express the number of elements.

  2. s:seq< ^a> -> ^b where ˆb is inferred by the result type. The cons here are that if ˆb is not inferred (and not defaulted to int) you'll get ambiguity errors, also it will move to the user the responsibility to use the appropriate result type.

@mvkra

This comment has been minimized.

Copy link
Author

@mvkra mvkra commented Nov 28, 2019

@gusty Its a good question. My makeshift code example was more that I saw a pretty typical use case that to a newcomer to the language should "just work" without having to understand SRTP errors. In my case I had a list of timings (in milliseconds of type int64) in a collection that I wanted to get the average of. LINQ in C# has overloads for a variety of different primitive number types. This means if I do a .Average() on a sequence of numbers usually "it just works" in that language. For newcomers to the language it isn't intuitive why it doesn't just work; or what the error message "int does not support the operator DivideByInt" means.

The current behavior (tested in FSI) that assumes the input type and return type are the same unless specified seems like a good default to me but I could be naive about some detail. In my case to be honest I wouldn't of minded if it returned a float or a decimal either.

open LanguagePrimitives

let inline average s = 
  let mutable sum = GenericZero
  let mutable items = GenericZero
  for i in s do
    sum <- sum + i
    items <- items + GenericOne
  sum / items

let int64Average = average [ 1L; 10L ] // 5L
let decimalAverage = average [ 1M; 2M ] // 1.5M
let floatAverageWithDecimalInput : float = average [ 1.0M; 20.0M ] // does not compile
@abelbraaksma

This comment has been minimized.

Copy link

@abelbraaksma abelbraaksma commented Nov 28, 2019

Perhaps just have an averageBy function, where the first argument is the division function?

There was a reason that the function requires that member (same as with several other core functions), since it's noon trivial how to get the average of, say, a sequence of bytes, or ints: what do you do with the remainder (they aren't floats, would you accept a potential huge rounding error? That's why they leave it up to the user), and how to deal with overflow?

For F# 5.0 I believe it is planned that extension methods can be used with SRTP, which would make this much simpler to solve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.