2013-05-21 117 views
4

如何通過參數類型的模式匹配在F#中起作用?F#按類型匹配模式

例如我試圖編寫簡單的程序,它將計算平方根,如果號碼提供或返回它的參數,否則。

open System 

let my_sqrt x = 
    match x with 
    | :? float as f -> sqrt f 
    | _ -> x 


printfn "Enter x" 
let x = Console.ReadLine() 

printfn "For x = %A result is %A" x (my_sqrt x) 

Console.ReadLine() 

我得到這個錯誤:

error FS0008: This runtime coercion or type test from type 
    'a  
to 
    float  
involves an indeterminate type based on information prior 
to this program point. Runtime type tests are not allowed 
on some types. Further type annotations are needed. 

由於sqrt作品與float我檢查float類型,但猜測可能有更好的解決辦法 - 像檢查,如果輸入的是(一般)如果是這樣,將其投入浮動?

回答

5

這裏的問題是x的類型其實是string。添加它來自Console.ReadLine,該字符串中存儲的信息類型只能在運行時確定。這意味着你不能在這裏既不使用模式匹配也不使用強制模式匹配。您可以使用Active Patterns。由於只有在運行時才知道實際數據存儲在x中,因此必須解析字符串並查看包含的內容。

因此,假設你期待float,但你不能確定,因爲用戶可以輸入任何他們想要的。我們將嘗試和分析我們的字符串:

let my_sqrt x = 
    let success, v = System.Single.TryParse x // the float in F# is represented by System.Single in .NET 
    if success then sqrt v 
    else x 

但這不會編譯:

This expression was expected to have type float32 but here has type string

的問題是,編譯器推斷返回一個float32功能的基礎上,表達sqrt (System.Single.Parse(x)) 。但是,如果x不解析爲浮點數,我們打算只返回它,並且因爲x是一個字符串,我們在這裏有不一致。

爲了解決這個問題,我們必須將結果轉換的sqrt字符串:

let my_sqrt x = 
    let success, v = System.Single.TryParse x 
    if success then (sqrt v).ToString() 
    else x 

確定,這應該工作,但它不使用模式匹配。所以,讓我們來定義「活躍」的格局,因爲我們不能用常規模式匹配這裏:

let (|Float|_|) input = 
    match System.Single.TryParse input with 
    | true, v -> Some v 
    | _ -> None 

基本上,如果input可以正確地解析爲一個浮點字面這種模式將只匹配。下面是它可以在你的初始功能的實現可以使用:

let my_sqrt' x = 
    match x with 
    | Float f -> (sqrt f).ToString() 
    | _ -> x 

這看起來很像你的功能,但是請注意,我仍然有添加.ToString()位。

希望這會有所幫助。

+0

如果「X」是不是字符串這將失敗 - 我以爲是這裏真正的挑戰。 –

+2

除非絕對必要,否則在活動模式(出於性能原因)內使用try/catch是一個非常糟糕的主意。在這種情況下,您應該使用'System.Single.TryParse'來嘗試解析該值,並且可以使用F#match語句的自動tupling功能來處理輸出。 –

+0

@JackP。很好,我會更新答案。 – MisterMetaphor

1

只是引用了一個只有斯科特Wlaschin的'F# for fun and profit' site

Matching on subtypes You can match on subtypes, using the :? operator, which gives you a crude polymorphism:

let x = new Object() 
let y = 
    match x with 
    | :? System.Int32 -> 
     printfn "matched an int" 
    | :? System.DateTime -> 
     printfn "matched a datetime" 
    | _ -> 
     printfn "another type" 

This only works to find subclasses of a parent class (in this case, Object). The overall type of the expression has the parent class as input.

Note that in some cases, you may need to 「box」 the value.

let detectType v = 
    match v with 
     | :? int -> printfn "this is an int" 
     | _ -> printfn "something else" 
// error FS0008: This runtime coercion or type test from type 'a to int 
// involves an indeterminate type based on information prior to this program point. 
// Runtime type tests are not allowed on some types. Further type annotations are needed. 

The message tells you the problem: 「runtime type tests are not allowed on some types」. The answer is to 「box」 the value which forces it into a reference type, and then you can type check it:

let detectTypeBoxed v = 
    match box v with  // used "box v" 
     | :? int -> printfn "this is an int" 
     | _ -> printfn "something else" 

//test 
detectTypeBoxed 1 
detectTypeBoxed 3.14 

In my opinion, matching and dispatching on types is a code smell, just as it is in object-oriented programming. It is occasionally necessary, but used carelessly is an indication of poor design.

In a good object oriented design, the correct approach would be to use polymorphism to replace the subtype tests, along with techniques such as double dispatch. So if you are doing this kind of OO in F#, you should probably use those same techniques.