2014-08-28 11 views
10

Bardzo często podczas pisania kodu rodzajowe w F # wpadnę sytuacji podobnej do tego (wiem, że to zupełnie nieefektywne, tylko dla celów demonstracyjnych):Jak mogę odrzucić powrót do typu, który wcześniej miał wartość?

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

Dla wielu problemów można używać statically resolved types i uzyskać nawet zwiększenie wydajności dzięki inline.

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) 

Powyższy kod nie zostanie skompilowany, ponieważ górny limit sekwencji jest zmiennoprzecinkowy. Nongenerically, mógłbym właśnie odesłać z powrotem do int na przykład.

Ale kompilator nie pozwoli mi użyć dowolnego z nich:

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

a te dwa prowadzą do InvalidCastException:

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

Również upcast i downcast są zabronione.

let sq = System.Convert.ChangeType(n |> float |> sqrt, n.GetType()) :?> ^a działa, ale wydaje się bardzo uciążliwe dla mnie.

Czy istnieje sposób, który przeoczyłem lub czy muszę używać ostatniej wersji? Ponieważ ostatnia z nich również pęknie na bigint, której potrzebuję dość często.

+0

Zrobiłem trochę skrzypce i najmilszym rozwiązaniem, jakie wymyśliłem, było zmodyfikowanie głównej biblioteki. Jeśli nie zależy Ci szczególnie na wydajności, zawsze możesz zwiększyć liczbę, dodając moce dwóch. –

+1

Dlaczego musisz przesyłać do 'float' przed wywołaniem' sqrt'? – Daniel

+0

@Daniel, ponieważ nie możesz (na przykład) przekazać int do sqrt: 'Typ 'int' nie obsługuje operatora 'Sqrt'' – phoog

Odpowiedz

4

Z trick FsControl możemy zdefiniować funkcję rodzajowe fromFloat:

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 został zdefiniowany here.

+0

Jeden połów - musisz ręcznie zdefiniować przeciążenie 'fromFloat' dla każdego typu liczbowego. –

+1

Możesz pobrać tę sztuczkę bezpośrednio, jak autor FsControl pokazuje np. [tutaj] (http://stackoverflow.com/questions/19682432/global-operator-overloading-in-f#answer-19687403): Zdefiniuj statyczne elementy jako operator '$' i ogólną funkcję 'let inline fromFloat (x : float):^a = (FromFloat $ Niezaznaczone.defaultof < ^a>) x' – kaefer

+0

OK, w końcu udało mi się to wypróbować i wygląda na to, że działa - ale nie rozumiem tego. Czy mógłbyś to rozwinąć, proszę (co robi 'Inline.instance', czy jest statycznie rozwiązane lub dynamicznie, i JAK)? – primfaktor

Powiązane problemy