2014-08-28 3 views
10

Очень часто при написании общего кода в F # Я прихожу в ситуации, подобной этой (я знаю, что это малоэффективным, только для демонстрационных целей):Как я могу вернуть обратно тип, который был ранее?

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

Для многих задач можно использовать statically resolved types и получить даже повышение производительности из-за 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) 

Код, указанный выше, не будет скомпилирован из-за того, что верхний предел последовательности является поплавком. В некотором роде, я мог бы просто вернуться к int, например.

Но компилятор не позволит мне использовать любой из них:

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

и эти два приводят к InvalidCastException:

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

Кроме того, upcast и downcast запрещены.

let sq = System.Convert.ChangeType(n |> float |> sqrt, n.GetType()) :?> ^a работает, но мне кажется очень громоздким.

Есть ли способ, которым я пропустил или мне действительно нужно использовать последнюю версию? Потому что последний будет разбит на bigint, что мне нужно довольно часто.

+0

Я немного поработал, и самое приятное решение, с которым я столкнулся, состояло в том, чтобы изменить основную библиотеку. Если вы не особенно заботитесь о производительности, вы всегда можете создать число, добавив полномочия двух. –

+1

Зачем вам нужно вводить 'float' перед вызовом' sqrt'? – Daniel

+0

@ Даниэль, потому что вы не можете (например) передать int в sqrt: 'Тип 'int' не поддерживает оператор 'Sqrt'' – phoog

ответ

4

С трик FsControl, мы можем определить обобщенную функцию 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 был определен here.

+0

Один улов - вам нужно вручную определить перегрузку 'fromFloat' для каждого числового типа. –

+1

Вы можете получить этот трюк напрямую, как показывает автор FsControl, например. [здесь] (http://stackoverflow.com/questions/19682432/global-operator-overloading-in-f#answer-19687403): Определите статические члены как '' 'operator и общую функцию' let inline fromFloat (x : float):^a = (FromFloat $ Unchecked.defaultof < ^a>) x' – kaefer

+0

Хорошо, я, наконец, пришел, чтобы попробовать это, и, похоже, он работает, но я этого не понимаю. Не могли бы вы продолжить это, пожалуйста (что делает 'Inline.instance', он статически разрешен или динамически, и КАК)? – primfaktor