2017-09-24 3 views
3

Ich versuche die dynamischen Fähigkeiten von F # für Situationen zu erforschen, in denen ich mit dem statischen System keine Funktion ausdrücken kann. Als solcher versuche ich eine mapN Funktion für (sagen wir) Option Typen, aber ich habe Probleme beim Erstellen einer Funktion mit einer dynamischen Anzahl von Argumenten. Ich habe versucht:Dynamische Funktionen in 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] 

(die die Funktion übergab und wenden ein Argument zu einem Zeitpunkt) die kompilieren, aber dann zur Laufzeit erhalte ich folgende:

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

Ich weiß, dass es idiomatischer wäre, entweder einen Berechnungsausdruck für Optionen zu erstellen oder map2 bis map5 oder so zu definieren, aber ich möchte speziell die dynamischen Fähigkeiten von F # untersuchen, um zu sehen, ob so etwas möglich wäre.

Ist dies nur ein Konzept, das in F # nicht möglich ist, oder gibt es einen Ansatz, den ich vermisse?

Antwort

2

Um zu erklären, warum Ihr Ansatz nicht funktioniert, ist das Problem, dass Sie keine Funktion vom Typ int -> int (dargestellt als FSharpFunc<int, int>) auf einen Wert von Typ obj -> obj (dargestellt werfen können als FSharpFunc<obj, obj>). Die Typen sind dieselben generischen Typen, aber die Umwandlung schlägt fehl, weil die generischen Parameter unterschiedlich sind.

Wenn Sie eine Menge von Boxen und Unboxing einsetzen, dann Ihre Funktion tatsächlich funktioniert, aber das ist wahrscheinlich etwas, das Sie nicht schreiben wollen:

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] 

Wenn Sie mehr Möglichkeiten erkunden, dank dynamischer Hacks wollte - Dann können Sie wahrscheinlich mehr mit F # Reflexion tun. Ich würde dies nicht verwenden in der Regel in der Produktion (einfach besser - ich würde nur mehrere Kartenfunktionen von Hand oder so ähnlich definieren), aber die folgenden Läufe:

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

danke für die Erklärung, warum meine Lösung nicht funktioniert und wie man es zur Arbeit bringt. Ich stimme zu, dass die dynamische Lösung ziemlich grob und nicht für die Produktion geeignet ist. Nicht das ändert meine Meinung zu F #, ich war nur neugierig auf die Fähigkeiten dort. –

4

Ich denke, Sie wären nur in der Lage, diesen Ansatz mit Reflexion zu nehmen.

Es gibt jedoch andere Möglichkeiten, das Gesamtproblem zu lösen, ohne dynamisch zu werden oder die anderen statischen Optionen zu verwenden, die Sie erwähnt haben. Sie können eine Menge der gleichen Bequemlichkeit mit Option.apply, die Sie selbst definieren müssen (oder aus einer Bibliothek nehmen). Dieser Code gestohlen wird und angepasst von 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

Für OP; Dies ist ein funktionelles "Muster" namens Applicative. Sehr nützlich und so wie ich es auch in einer Sprache wie F # machen würde. In statisch typisierten Sprachen, die keine partielle Anwendung (C#, Kotlin, Java usw.) unterstützen, würde ich eine Überladung für 1 Argument, 2 Argumente usw. mit einem Code-Gen-Tool erzeugen. In C++ gibt es Unterstützung für variadic generische Argumentlisten, so dass wir eine Funktion mapN schreiben können, die wie OP-Versionen funktioniert. – FuleSnabel

+0

@TheQuickBrownFox, ich hatte diesen Artikel über F # For Fun und Profit über die 'apply'-Funktion gelesen, aber das hatte ich komplett vergessen. Danke für den Hinweis auf seine Verwendung hier –