F# 中测量单位的模式匹配
该函数:
let convert (v: float<_>) =
match v with
| :? float<m> -> v / 0.1<m>
| :? float<m/s> -> v / 0.2<m/s>
| _ -> failwith "unknown"
产生错误
类型“float<'u>'”没有任何适当的子类型,不能用作类型测试或运行时强制的源。
有什么方法可以匹配度量单位吗?
This function:
let convert (v: float<_>) =
match v with
| :? float<m> -> v / 0.1<m>
| :? float<m/s> -> v / 0.2<m/s>
| _ -> failwith "unknown"
produces an error
The type 'float<'u>' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion.
Is there any way how to pattern match units of measure?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
正如@kvb 详细解释的那样,问题在于度量单位是类型的一部分。这意味着
float
与float
的类型不同(不幸的是,此信息在运行时不会作为值的一部分存储)。因此,您实际上是在尝试编写一个可以处理两种不同类型输入的函数。干净的函数解决方案是声明一个可区分的联合,它可以保存第一种类型或第二种类型的值:
然后您可以使用普通模式匹配编写该函数:
您需要将这些值显式包装到可区分的联合值中,但这可能是直接执行此操作的唯一方法(无需对程序结构进行一些较大的更改)。
对于
int
和float
等普通类型,您还可以使用重载成员(在某些 F# 类型中声明),但这不适用于度量单位,因为签名F#编译器擦除单元信息后将相同。As @kvb explains in detail, the problem is that units of measure are a part of the type. This means that
float<m>
is different type thanfloat<m/s>
(and unfortunately, this information isn't stored as part of the value at runtime).So, you're actually trying to write a function that would work with two different types of input. The clean functional solution is to declare a discriminated union that can hold values of either the first type or the second type:
Then you can write the function using ordinary pattern matching:
You'll need to explicitly wrap the values into the discriminated union value, but it's probably the only way to do this directly (without making some larger changes in the program structure).
For normal types like
int
andfloat
, you could also use overloaded members (declared in some F# type), but that doesn't work for units of measure, because the signature will be the same after the F# compiler erases the unit information.您的方法有两个问题。首先,当您在函数定义中使用下划线时,这与使用新类型变量相同,因此您的定义等效于以下内容:
错误消息告诉您编译器知道
v
的类型为float<'u>
,并且float<'u>
没有适当的子类型,因此进行类型测试是没有意义的确定它是float
还是任何其他类型。您可以尝试通过首先将
v
装箱到对象中然后进行类型测试来解决此问题。例如,如果您有一个list<'a>
并且想查看它是否是一个list
,这是可行的,因为有关泛型对象的完整类型信息在运行时进行跟踪,包括泛型类型参数(值得注意的是,这与 Java 等其他运行时的工作方式不同)。不幸的是,F# 测量单位在运行时被删除,因此这在这里不起作用 - 系统无法在给定盒装表示的情况下推断出正确的测量类型,因为在运行时该值只是一个普通的浮点数 - F# 的度量单位系统实际上在这方面与 Java 处理泛型类型的方式非常相似。顺便说一句,你想要做的事情似乎很可疑 - 以度量单位为通用的函数不应该根据度量类型做不同的事情;它们应该是正确参数化的。您究竟想要实现什么目标?它看起来当然不像是与物理现实相对应的操作,而物理现实是 F# 度量类型的基础。
There are two problems with your approach. First of all, when you use an underscore in the definition of your function, that's the same as using a fresh type variable, so your definition is equivalent to the following:
What the error message is telling you is that the compiler know that
v
is of typefloat<'u>
, andfloat<'u>
has no proper subtypes, so there's no point in doing a type test to determine if it's afloat<m>
or any other type.You might try to get around this by first boxing
v
into an object and then doing a type test. This would work, for instance, if you had alist<'a>
and wanted to see if it were alist<int>
, because full type information about generic objects is tracked at runtime including generic type parameters (notably, this is different from how some other runtimes like Java's work). Unfortunately, F# units of measure are erased at runtime, so this won't work here - there is no way for the system to infer the correct measure type given a boxed representation, since at runtime the value is just a plainfloat
- F#'s system for units of measure is actually quite similar in this respect to how Java handles generic types.As an aside, what you're trying to do seems quite suspect - functions which are generic in the unit of measure shouldn't do different things depending on what the measure type is; they should be properly parametric. What exactly are you trying to achieve? It certainly doesn't look like an operation which corresponds to physical reality, which is the basis for F#'s measure types.
请参阅http://msdn 中的运行时单位部分.microsoft.com/en-us/library/dd233243.aspx。
我同意@kvb,我认为解决这个问题的最好方法是传递一个对象。
我想做的,使用你的代码结构:
See the Units at Runtime Section at http://msdn.microsoft.com/en-us/library/dd233243.aspx.
I agree with @kvb, I think the best way around this is to pass an object.
What I would like to do, using your code structure: