2017-09-24 47 views
3

我試圖探索F#的動態功能,以適應靜態類型系統無法表達某些功能的情況。因此,我試圖爲(比如)Option類型創建一個mapN函數,但我在創建具有動態數量參數的函數時遇到問題。我已經試過:F#中的動態函數

let mapN<'output> (f : obj) args = 
    let rec mapN' (state:obj) (args' : (obj option) list) = 
    match args' with 
    | Some x :: xs -> mapN' ((state :?> obj -> obj) x) xs 
    | None _ :: _ -> None 
    | [] -> state :?> 'output option 

    mapN' f args 

let toObjOption (x : #obj option) = 
    Option.map (fun x -> x :> obj) x 

let a = Some 5 
let b = Some "hi" 
let c = Some true 

let ans = mapN<string> (fun x y z -> sprintf "%i %s %A" x y z) [a |> toObjOption; b |> toObjOption; c |> toObjOption] 

(這需要傳遞的功能,並在同一時間將一個參數),它編譯,但隨後在運行時,我得到以下幾點:

System.InvalidCastException: Unable to cast object of type '[email protected]' to type 
'Microsoft.FSharp.Core.FSharpFunc`2[System.Object,System.Object]'. 

我知道或者爲選項創建計算表達式,或者定義map2map5左右,但我特別想探索F#的動態功能以查看這樣的事情是否可行。

這僅僅是一個無法在F#中完成的概念,還是存在我失蹤的方法?

回答

2

要解釋爲什麼你的方法是行不通的,問題是,你不能施放int -> int型(表示爲FSharpFunc<int, int>)的函數obj -> obj類型的值(爲代表的作爲FSharpFunc<obj, obj>)。類型是相同的泛型類型,但由於泛型參數不同,轉換失敗。

如果插入了大量裝箱和拆箱的,那麼你的函數的實際工作,但是這可能不是你想要寫的東西:

let ans = mapN<string> (fun (x:obj) -> box (fun (y:obj) -> box (fun (z:obj) -> 
    box (Some(sprintf "%i %s %A" (unbox x) (unbox y) (unbox z)))))) 
    [a |> toObjOption; b |> toObjOption; c |> toObjOption] 

如果你想探索更多的選擇可能要歸功於動態黑客 - 那麼你可以使用F#反射來做更多的事情。我通常不會在生產中使用它(簡單更好 - 我只是通過手工或類似的方式定義多個地圖功能),但以下運行:

let rec mapN<'R> f args = 
    match args with 
    | [] -> unbox<'R> f 
    | x::xs -> 
     let m = f.GetType().GetMethods() |> Seq.find (fun m -> 
     m.Name = "Invoke" && m.GetParameters().Length = 1) 
     mapN<'R> (m.Invoke(f, [| x |])) xs 

mapN<obj> (fun a b c -> sprintf "%d %s %A" a b c) [box 1; box "hi"; box true] 
+1

感謝您解釋爲什麼我的解決方案無法正常工作以及如何使其工作。我同意動態解決方案相當粗糙,不適合生產。這並不改變我對F#的看法,我只是對那裏的能力感到好奇。 –

4

我想你只能通過反思採取這種方法。

但是,還有其他方法可以解決整體問題,而無需動態或使用您提到的其他靜態選項。您可以使用Option.apply獲得很多相同的便利,您需要自定義(或從圖書館中獲取)。此代碼被盜,改編自F# for fun and profit

module Option = 
    let apply fOpt xOpt = 
     match fOpt,xOpt with 
     | Some f, Some x -> Some (f x) 
     | _ -> None 

let resultOption = 
    let (<*>) = Option.apply 

    Some (fun x y z -> sprintf "%i %s %A" x y z) 
    <*> Some 5 
    <*> Some "hi" 
    <*> Some true 
+0

對於OP;這是一個稱爲Applicative的功能性「模式」。非常有用,也是我用F#這樣的語言來完成的。在不支持部分應用程序(C#,kotlin,java等)的靜態類型語言中,我會使用代碼生成工具爲1個參數,2個參數等生成重載。在C++中,支持各種泛型參數列表,所以我們可以編寫一個像OP一樣工作的函數mapN。 – FuleSnabel

+0

@TheQuickBrownFox,我已經閱讀過關於F#Fun和Profit關於'apply'函數的文章,但我完全忘記了這一點。感謝您指出在此使用 –