2016-10-05 43 views
4

我有以下代码:为什么在F#模式匹配中某些表达式中的泛型类型与obj相匹配?

type Message<'a> = | Message of 'a 

let handleMessage message = 
    match box message with 
    | :? Message<_> -> printfn "Message" 
    | _ -> printfn "Not message" 

let handleMessageEx message = 
    match box message with 
    | :? Message<int> -> printfn "Message" 
    | _ -> printfn "Not message" 

handleMessage <| Message 1 
handleMessage <| Message (1 :> obj) 
handleMessageEx <| Message 1 

在F#交互式输出如下:

Not message 
Message 
Message 

为什么第一条语句结果“没有消息”?即当匹配装箱值F#无法检测到它是通用类型消息< _>并且除非我指定底层类型,否则它将其设置为对象(因此在(消息1)上失败匹配)。

+1

不能很好地解释,所以只是一个评论,但泛型类型不是一个真正的类型,所以不能成为模式类型测试的目标。类型测试需要一个“具体”类型,所以通配符被可能的'obj'最普通的东西代替。你可以使函数本身也是通用的'handleMessage <'a>'',但是你需要把通用参数放在调用站点以使它工作'handleMessage <|消息1'(没有你也会得到“Not message”) – Sehnsucht

+1

问题是obj并不是F#世界中最普通的东西,因为它不会匹配具体的类型。我想知道为什么在消息<'a>的情况下F#不会离开'开放,可用于任何匹配。 –

回答

4

只要你看到_作为类型参数,就把它想象成一个你不关心的新类型参数。如果我们据此调整你的第一个定义:

let handleMessage message = 
    match box message with 
    | :? Message<'_a> -> printfn "Message" 
    | _ -> printfn "Not message" 

那么编译器为我们提供了这个有用的线索:

警告FS0064:此构造导致代码比由类型注释所列的那样普通。类型变量'_a被约束为类型'obj'。

的问题是,该类型参数必须要给出一些具体的,但是编译器上没有依据挑一个,这样它默认为obj,这是不是你想要的。

开箱即用并没有很好的方法,但您可以创建一个活动模式以简化体验:https://stackoverflow.com/a/2140485/82959

+1

“问题是,类型参数必须给定一些特定的值,但编译器没有选择其中的基础,因此它默认为obj,这不是您想要的。” 这是我不明白的。 F#类型推断通常不会像这样工作。如果我定义了一个函数 let func(v:'a)= printfn“%A”v 那么'a将不会被赋予某个特定值(obj),它将保持未绑定状态,因为格式指定“%A”不绑定到任何类型。为什么在其他情况下执行绑定? –

+1

而对于具有活动模式的解决方法的链接 - 看起来像我需要做这样的事情。 –

+1

@VagifAbilov因为printfn“%A”不关心赋予它的实际类型;它处理任何事情。在相反的类型测试中放入一个“约束”来具体测试一个具体类型'Message <'a>'不是一个具体的类型,函数也是通用的,因为每次使用新的泛型参数(int,string,whatever)这个特定类型的代码将被编译,其中'a将被该类型有效地取代预期的具体类型('消息'或'消息)' – Sehnsucht

3

简而言之,Message<_>并不意味着你期望它是通用的,这意味着编译器可以自由地推断出Message的泛型类型参数。在没有其他限制的情况下,其结果是使其成为obj。如果你做的功能一般也会发生这种情况 - 泛型类型参数的函数(以及由此延伸,以Message类型检查)在调用点决定:

let handleMessage<'a> (message: obj) = 
    match message with 
    | :? Message<'a> -> printfn "Message" 
    | _ -> printfn "Not message" 

handleMessage <| Message 1  // Not message -> 'a inferred to be obj 
handleMessage<int> <| Message 1 // Message -> 'a inferred to be int 

你可能想要做的是什么像这样:

let handleMessage message = 
    let typ = message.GetType() 
    match typ with 
    | _ when typ.GetGenericTypeDefinition() = typedefof<Message<_>> -> printfn "Message" 
    | _ -> printfn "Not message" 

handleMessage <| Message 1 
handleMessage <| Message (1 :> obj) 

这里不是做一个类型测试,而是检查两者的泛型类型定义。

+1

我也可以写消息<'a>得到相同的结果 - 'a会匹配成为obj,因为尽管obj是所有类型的根,但它与F#模式匹配中的匹配并不匹配。 当然,反射总是可以提供解决方法,但是我希望将此选项作为最后的手段,部分原因是效率低下,并且此代码是日志记录功能的一部分,这使得其性能至关重要。 –

+0

你测过了吗?这个函数做什么很微不足道,为什么它会很慢。 – scrwtp

+0

这些特殊的反射方法不会减慢速度,但我简化了我的情况 - 我不仅想匹配消息,还要绑定匹配结果。通用的Message类型实际上是一个元组的包装,我需要提取这些元组的值。所有这些使用反射。 –