简体   繁体   中英

How to write a variadic function in F# emulating a similar Haskell solution?

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.

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 .

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM