2017-09-24 7 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]'. 

私が実現しますオプションの計算式を作成するか、map2からmap5までを定義するのはもっと慣れていると思いますが、特にF#の動的機能を調べてこのようなことが可能かどうかを確認したいと考えています。

これはF#では実行できない概念ですか、それとも私が逃しているアプローチですか?

答えて

2

あなたのアプローチが動作しない理由を説明するために、問題はあなたがタイプobj -> objの値に(FSharpFunc<int, int>として表される)タイプint -> intの機能をキャストすることができないということです(表現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など)をサポートしていない静的型言語では、コードgenツールを使用して1引数、2引数などのオーバーロードを生成します。 C++ではvariadic汎用引数リストがサポートされていますので、OPのように機能するmapN関数を記述することができます。 – FuleSnabel

+0

@TheQuickBrownFox、私はF#でこの記事を読んでいましたが、 'apply'関数についての楽しみと利益のために、私はそれを完全に忘れていました。ここでその使用を指摘していただきありがとうございます –