2013-05-21 82 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.