Given the following expression to sum an IEnumerable of numbers:
let sum l = l |> Seq.reduce(+) //version a
is it possible to eliminate the argument--like so?
let sum = Seq.reduce(+) //version b
I get an error from the F# compiler (FS0030) and I seem to recall having seen something about an "eta conversion" being involved but unfortunately my knowledge of lambda calc is too limited to follow how eta conversion is involved.
Can the argument be eliminated as in version b?
Would someone please point me to literature that would explain an eta conversion and how it would come into play in this particular piece of code?
FS0030:
stdin(1,5): error FS0030: Value restriction. The value 'sum' has been
inferred to have generic type
val sum : ('_a -> int) when '_a :> seq Either make the arguments to 'sum' explicit or, if you do not intend for it to be
generic, add a type annotation.
"Eta conversion" simply means adding or removing the argument. The problem you are hitting is called value restriction. In ML languages, a value declared as a value, ie. declared without explicit arguments, cannot have a generic type, even if it has a function type. Here is some relevant literature. The idea is to prevent a ref cell from holding values of different types. For example, without value restriction, the following program would be allowed:
let f : 'a -> 'a option =
let r = ref None
fun x ->
let old = !r
r := Some x
old
f 3 // r := Some 3; returns None : int option
f "t" // r := Some "t"; returns Some 3 : string option!!!
As kvb said, if you do not intend the function to be generic, then you can add a type signature and use point-free style.
You can do it in point free style, but you need to add a (monomorphic) type annotation:
let sum : int seq -> int = Seq.reduce (+)
A point-free function is a value.
As other answers say, F# does not allow generic values. However, it perfectly allows generic functions. Let's convert sum
into a function by adding a fake unit
parameter:
let sum_attempt1() = Seq.reduce (+)
let v1 = [1.0; 2.0] |> sum() // float
// inferred by first usage:
// val sum_attempt1: unit -> (seq<float> -> float)
This works, although it is not yet generic. Marking the function inline
does the trick:
let inline sum() = Seq.reduce (+)
// val sum: unit -> (seq<'a> -> 'a)
// Use
let v1 = [1; 2] |> sum() // int
let v2 = [1.0; 2.0] |> sum() // float
let v3 = ["foo"; "bar"] |> sum() // string