2014-08-28 9 views
10

oft sehr, wenn generischen Code in F # Schreiben komme ich von einer ähnlichen Situation wie diese (ich weiß, das ziemlich ineffizient, nur zu Demonstrationszwecken ist):Wie kann ich einen Wert auf einen Typ zurückgeben, den ein Wert zuvor hatte?

let isPrime n = 
    let sq = n |> float |> sqrt |> int 
    {2..sq} |> Seq.forall (fun d -> n % d <> 0) 

Für viele Probleme kann ich statically resolved types verwenden und erhalte sogar ein Leistungsschub durch Inlining.

let inline isPrime (n:^a) = 
    let two = LanguagePrimitives.GenericOne + LanguagePrimitives.GenericOne 
    let sq = n |> float |> sqrt |> int 
    {two..sq} |> Seq.forall (fun d -> n % d <> LanguagePrimitives.GenericZero) 

Der obige Code wird nicht kompiliert, da die obere Sequenzgrenze ein Float ist. Nongenerisch könnte ich zum Beispiel einfach auf int zurückwerfen.

Aber der Compiler wird mich nicht davon verwenden lassen:

  • let sq = n |> float |> sqrt :> ^a
  • let sq = n |> float |> sqrt :?> ^a

und diese beiden führen zu einem InvalidCastException:

  • let sq = n |> float |> sqrt |> box |> :?> ^a
  • let sq = n |> float |> sqrt |> box |> unbox

Auch upcast und downcast sind verboten.

let sq = System.Convert.ChangeType(n |> float |> sqrt, n.GetType()) :?> ^a funktioniert, scheint mir aber sehr umständlich.

Gibt es einen Weg, den ich übersehen habe oder muss ich wirklich die letzte Version verwenden? Denn der letzte wird auch für bigint brechen, was ich ziemlich oft brauche.

+0

Ich habe ein bisschen herumgespielt und die beste Lösung, die ich mir ausgedacht habe, war die Kernbibliothek zu modifizieren. Wenn Ihnen die Leistung nicht besonders wichtig ist, können Sie die Nummer immer durch Hinzufügen von Zweierpotenzen aufbauen. –

+1

Warum müssen Sie vor dem Aufruf von 'sqrt' in' float' umwandeln? – Daniel

+0

@Daniel weil Sie (zB) kein int an sqrt übergeben können: 'Der Typ 'int' unterstützt nicht den Operator 'Sqrt'' – phoog

Antwort

4

Mit dem Trick von FsControl können wir generische Funktion fromFloat definieren:

open FsControl.Core 

type FromFloat = FromFloat with 
    static member instance (FromFloat, _:int32) = fun (x:float) -> int x 
    static member instance (FromFloat, _:int64) = fun (x:float) -> int64 x 
    static member instance (FromFloat, _:bigint) = fun (x:float) -> bigint x 
let inline fromFloat (x:float):^a = Inline.instance FromFloat x 

let inline isPrime (n:^a) = 
    let two = LanguagePrimitives.GenericOne + LanguagePrimitives.GenericOne 
    let sq = n |> float |> sqrt |> fromFloat 
    {two..sq} |> Seq.forall (fun d -> n % d <> LanguagePrimitives.GenericZero) 

printfn "%A" <| isPrime 71 
printfn "%A" <| isPrime 6L 
printfn "%A" <| isPrime 23I 

Inline.instance here definiert wurde.

+0

Ein Haken - Sie müssen die 'fromFloat'-Überladung für jeden numerischen Typ manuell definieren. –

+1

Sie könnten diesen Trick direkt ausführen, da der Autor von FsControl z. [hier] (http://stackoverflow.com/questions/19682432/global-operator-overloading-in-f#answer-19687403): Definieren Sie die statischen Elemente als '$' -Operator und die generische Funktion 'inline fromFloat (x : float):^a = (FromFloat $ Deaktiviert.defaultof < ^a>) x' – kaefer

+0

Ok, ich kam endlich dazu, dies zu versuchen, und es scheint zu funktionieren - aber ich verstehe es nicht. Könnten Sie das bitte weiter ausführen (was macht "Inline.instance", ist es statisch aufgelöst oder dynamisch und WIE)? – primfaktor

Verwandte Themen