4

How pattern-matching by type of argument works in F#?

For example I'm trying to write simple program which would calculate square root if number provided or return it's argument otherwise.

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()

I get this error:

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.

Since sqrt works with float I check for float type, but guess there could be better solution - like check if input is number (in general) and if so, cast it to float?

4

2 回答 2

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

但这不会编译:

此表达式应为 float32 类型,但此处为 string 类型

float32问题是编译器根据表达式推断函数返回 a 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()位。

希望这可以帮助。

于 2013-05-21T12:02:06.343 回答
2

只需引用唯一的 Scott Wlaschin 的“F# for fun and profit”网站

匹配子类型 您可以匹配子类型,使用 :? 运算符,它为您提供粗略的多态性:

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

这仅适用于查找父类(在本例中为 Object)的子类。表达式的整体类型以父类作为输入。

请注意,在某些情况下,您可能需要“装箱”该值。

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.

该消息告诉您问题:“某些类型不允许运行时类型测试”。答案是将强制它的值“装箱”为引用类型,然后您可以键入检查它:

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

在我看来,类型的匹配和分派是一种代码味道,就像在面向对象编程中一样。它有时是必要的,但不小心使用表明设计不佳。

在一个好的面向对象设计中,正确的方法是使用多态性来替换子类型测试,以及双重分派等技术。因此,如果您在 F# 中执行这种 OO,您可能应该使用相同的技术。

于 2017-07-28T16:08:59.130 回答