问题
How can I (if at all) emulate variadic functions (not methods) so that I could write
sum 1 2 3
sum 1 2 3 4 5
sum 1 2 3 4 5 6 7
// etc.
The code above is just meant as an example - obviously if I would have to sum up a list then
[ 1; 2 ; 3] |> List.sum
is a much better way.
However I am looking for a structurally similar solution like this Haskell solution
What is also important is that the normal syntax for function calls and parameter values remains the same. So
sum 1 2 3
vs
sum(1, 2, 3)
which effectively means that
let sum ([<ParamArray>] arr) = ...
is not wanted in this specific case.
The motivation for all of this: I am exploring the outer fringes of F#'s type system and syntax. And I am fully aware of that I might have crossed the boundary of what is possible already.
PS: my concrete ideas (which I have not described here) can also be solved completely differently - so I know and so I have done already. Therefore my question is not: how can this be solved differently but how can this be solved structurally like Haskell.
PPS: Double Karma-Points if you can make the whole solution recursive.
回答1:
You said function, not method. So ParamArray
is not an option.
The Haskell code you linked is based on the inferred result type.
Here's a way to resolve based on the inferred result type in F#:
type T = T with
static member inline ($) (T, r:'t->'t ) = fun a b -> a + b
static member inline ($) (T, r:'t->'t->'t ) = fun a b c -> a + b + c
static member inline ($) (T, r:'t->'t->'t->'t) = fun a b c d -> a + b + c + d
let inline sum (x:'a) :'r = (T $ Unchecked.defaultof<'r>) x
let x:int = sum 2 3
let y:int = sum 2 3 4
let z:int = sum 2 3 4 5
let d:decimal = sum 2M 3M 4M
let mult3Numbers a b c = a * b * c
let res2 = mult3Numbers 3 (sum 3 4 ) 10
let res3 = mult3Numbers 3 (sum 3 4 5) 10
UPDATE
The above code doesn't work anymore as from F# 4.1 (see the comments) but here's a better example with a recursive polyvariadic function taking n (unlimited) arguments:
type T = T with
static member ($) (T, _:int ) = (+)
static member ($) (T, _:decimal) = (+)
let inline sum (i:'a) (x:'a) :'r = (T $ Unchecked.defaultof<'r>) i x
type T with
static member inline ($) (T, _:'t-> 'rest) = fun (a:'t) -> (+) a >> sum
let x:int = sum 2 3
let y:int = sum 2 3 4
let z:int = sum 2 3 4 5
let d:decimal = sum 2M 3M 4M
let mult3Numbers a b c = a * b * c
let res2 = mult3Numbers 3 (sum 3 4) (sum 2 2 3 3)
let res3 = mult3Numbers 3 (sum 3 4 5 11 13 20) 10
You can also have a look at this polyvariadic fold.
回答2:
As mentioned in the comments, you can use the ParamArray
attribute in F# and this will let you call the function with multiple parameters - although you'll have to use the .NET notation and write sum(1,2,3,4,5,6)
.
That said, I probably wouldn't do this in practice. If you're writing a function that takes an input consisting of an unknown number of values, then using a list is likely a better design:
List.sum [1; 2; 3 ]
List.sum [1; 2; 3; 4; 5 ]
List.sum [1; 2; 3; 4; 5; 6; 7 ]
This is only a few more characters and it better models the problem that you're solving - at least, based on the toy example you posted here.
It is hard to give a good answer without knowing what is the problem that you are actually solving. But in general, I think taking a list is a good F#-friendly default. Using ParamArray
is useful in some cases and for C# interop.
来源:https://stackoverflow.com/questions/28243963/how-to-write-a-variadic-function-in-f-emulating-a-similar-haskell-solution