3
votes

While it is trivial to pass a partial function into another function, how can I return partial functions of varying signatures from a function?

Here is the basic code I am attempting, followed by various attempts to get it to work:

type InitData() =
    static member arrayIntAsc count = [|1..count|] 
    static member seqIntAsc count = {1..count}
    static member listIntAsc count = [1..count]
    (*more diverse signatures*)

module x =
    let getInitDataFun (initData:string) =
        match initData.ToLower() with
        | "arrayintasc" -> InitData.arrayIntAsc
        | "seqintasc" -> InitData.seqIntAsc
        | "listintasc" -> InitData.listIntAsc
        (*more diverse signatures*)
        | _ -> failwithf "InitData function %s not recognized" initData
  1. tried forcing a generic return signature in various ways, but F# 3.0 always forced the getInitDataFun return signature to the signature of the very first match:

    let getInitDataFun (initData:string) : 'a -> 'b  = ...
    let getInitDataFun (initData:string) : _ -> _  = ...
    let getInitDataFun (initData:string) : int -> #(int seq)  = ...
    let getInitDataFun (initData:string) : int -> #('a seq)  = ...
    (*even if I could get (int -> #(int seq)) to work, I would like to return
      signatures not in this pattern too*)
    
  2. Tried box/unbox:

    | "arrayintasc" -> box InitData.arrayIntAsc
    

    this compiles, but the unbox attempt throws a runtime error:

    Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'RangeInt32@4819-2' to type 'System.Collections.Generic.IEnumerable`1[System.Object]'

  3. Tried returning the partial functions as quotations, but had similar problems. If I returned typed quotations, had the same problem with returning different Expr signatures. I could returned untyped quotations, but then I have to know on the calling side the signature of the returned untyped expression.

  4. Considered reflection, but basically the same issue with a need to know the actual signature when it comes time to invoke.

  5. Tried upcasting the partial functions in various ways too.

2
Could you explain why you want to do this? How do you expect callers to use getInitDataFun? - kvb

2 Answers

2
votes

Your best bet is to change the static members to all return the same type:

type InitData() =
    static member arrayIntAsc count = seq [|1..count|] 
    static member seqIntAsc count = {1..count}
    static member listIntAsc count = seq [1..count]

or wrap them with a function that does the cast:

let getInitDataFun (initData:string) =
    let asSeq f x = f x :> seq<_>
    match initData.ToLower() with
    | "arrayintasc" -> asSeq InitData.arrayIntAsc
    | "seqintasc" -> InitData.seqIntAsc
    | "listintasc" -> asSeq InitData.listIntAsc

You could make it generic:

let getInitDataFun<'T when 'T :> seq<int>> (initData:string) : (int -> 'T) =
    match initData.ToLower() with
    | "arrayintasc" -> (box >> unbox) InitData.arrayIntAsc
    | "seqintasc" -> (box >> unbox) InitData.seqIntAsc
    | "listintasc" -> (box >> unbox) InitData.listIntAsc

but it would generate a runtime exception if the wrong return type was expected:

let f = getInitDataFun "arrayintasc"
let x : int list = f 10 //BOOM!
1
votes

I think either this is what you want

type InitData() = 
    static member arrayIntAsc count = [|1..count|]  
    static member seqIntAsc count = {1..count} 
    static member listIntAsc count = [1..count] 
    (*more diverse signatures*) 

let getInitDataFun (initData:string) : obj = 
    match initData.ToLower() with 
    | "arrayintasc" -> box InitData.arrayIntAsc 
    | "seqintasc" -> box InitData.seqIntAsc 
    | "listintasc" -> box InitData.listIntAsc 
    (*more diverse signatures*) 
    | _ -> failwithf "InitData function %s not recognized" initData 

let a = ((getInitDataFun "arrayintasc") :?> int->int[]) 20
printfn "%A" a

or I'm unclear what you're asking.