F# 元组类型的模式匹配
我有一个柯里化函数,我希望它支持不同类型的参数,这些参数不属于继承关系:
type MyType1 = A | B of float
type MyType2 = C | D of int
我试图做的是:
let func x y =
match (x, y) with
| :? Tuple<MyType1, MyType1> -> "1, 1"
| _ -> "..."
但是这是不可能的。 F# 抱怨:
类型 ''a * 'b' 没有任何适当的子类型,不能用作类型测试或运行时强制的源。
执行此操作的优雅方法是什么?
编辑:让我尝试澄清这一点。
我有两种相似但不同的类型。我可以非常轻松地将一种类型转换为另一种类型。我想定义一个将作用于这些类型的实体的二元操作,但我想向客户端公开单个操作。
也就是说,不是提供:
let op11 (x : MyType1) (y : MyType1) = // do something useful
let op12 (x : MyType1) (y : MyType2) =
// convert y to MyType1
let y' = // ...
// dispatch to op11
op11 x y'
let op21 (x : MyType2) (y : MyType1) = // similar
let op22 (x : MyType2) (y : MyType2) = // similar
我想要的是向客户端代码公开单个函数:
let op (x : obj) (y : obj) = // ...
这就像模拟方法重载的行为,但使用柯里化函数。
I have a curried function that I'd like it to support different types of parameters, that are not on a inheritance relationship:
type MyType1 = A | B of float
type MyType2 = C | D of int
What I tried to do is:
let func x y =
match (x, y) with
| :? Tuple<MyType1, MyType1> -> "1, 1"
| _ -> "..."
However this is not possible. F# complains:
The type ''a * 'b' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion.
What is an elegant way to do this?
EDIT: Let me try to clarify this.
I have two similar, but distinct, types. I can very easily convert one type to another. I want to define a binary operation that will act on entities of those types, but I'd like to expose a single operation to the client.
That is, instead of providing:
let op11 (x : MyType1) (y : MyType1) = // do something useful
let op12 (x : MyType1) (y : MyType2) =
// convert y to MyType1
let y' = // ...
// dispatch to op11
op11 x y'
let op21 (x : MyType2) (y : MyType1) = // similar
let op22 (x : MyType2) (y : MyType2) = // similar
what I would like is to expose a single function to client code:
let op (x : obj) (y : obj) = // ...
This is like simulating the behavior of method overloading, but with curried functions.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您的代码不起作用,因为 F# 将参数类型概括为类型参数。我认为您无法动态测试类型
'a * 'b
是否可以转换为类型MyType1 * MyType2
(尽管这对我来说有点令人困惑)。在任何情况下,您都可以编写一个函数,该函数接受两个obj
类型的参数,并使用两个:?
模式分别测试它们:无论如何,知道为什么这样做会很有趣你想这样做吗?可能有更好的解决方案...
编辑 (2) 因此,从
T1
和T2
的所有 4 个二元素组合中,您想要可以采用 3 的函数。正确吗(T1 * T1
、T1 * T2
和T2 * T2
)?在这种情况下,您无法编写完全安全的柯里化函数,因为第二个参数的类型将“依赖于”第一个参数的类型(如果第一个参数的类型为T2
,则第二个参数也必须是T2
(否则也可以是T1
))。您可以编写一个安全的非柯里化函数,该函数采用以下类型的参数:
该函数的类型为
MyArg ->字符串
。如果您想要一个柯里化函数,您可以定义一个类型,允许您使用
T1
或T2
作为第一个和第二个参数。然后,你的柯里化函数将是
MyArg ->我的Arg ->字符串
。但请注意,如果不允许使用一种参数类型组合(如果我理解正确,则不应允许使用T2 * T1
)。在这种情况下,您的函数只需抛出异常或类似的东西。Your code doesn't work, because F# generalizes the type of arguments to a type parameter. I think you can't dynamically test whether a type
'a * 'b
can be converted to typeMyType1 * MyType2
(though this is a bit confusing to me). In any case, you can write a function that takes two arguments of typeobj
and tests them separately using two:?
patterns:Anyway, it would be interesting to know why do you want to do this? There may be a better solution...
EDIT (2) So, from all 4 two-element combinations of
T1
andT2
, you want the function that can take 3. Is that correct (T1 * T1
,T1 * T2
andT2 * T2
)? In that case, you can't write a fully safe curried function, because the type of second argument would "depend" on the type of first argument (if the first argument has a typeT2
, then the second argument also has to beT2
(otherwise it can beT1
too)).You can write a safe non-curried function that takes an argument of the following type:
The type of the function would be
MyArg -> string
.If you want a curried function, you can define a type which allows you to use either
T1
orT2
as both first and second argument.Then, your curried function will be
MyArg -> MyArg -> string
. But note that if one combination of argument types is not allowed (if I understand you correctly,T2 * T1
shouldn't be allowed). In this case, your function will simply have to throw an exception or something like that.本质上有三种不同的方法可以实现这一点。
第一个是按照您的建议通过向上转换为
obj
来牺牲静态类型:这实际上总是一个糟糕的解决方案,因为它会导致引入不必要的运行时类型检查:
唯一 我看到这个工作很好的地方是专门为用户从 F# 交互式会话中调用而设计的库代码,其中编译时和运行时类型错误实际上同时发生,并且动态类型可以更简洁。例如,我们的 F# for Visualization 库允许用户尝试可视化任何值使用此技术为不同(已知)类型调用自定义可视化例程的任何类型(了解更多)。
第二种是将两种单独的类型替换为单一类型:
第三种解决方案是引入一种新类型来统一这两种类型:
There are essentially three different ways to accomplish this.
The first is to sacrifice static typing by upcasting to
obj
as you suggested:This is virtually always an awful solution because it results in the introduction of unnecessary run-time type checks:
The only place I have seen this work well is library code designed specifically for users to invoke from F# interactive sessions where compile-time and run-time type errors effectively occur at the same time and dynamic typing can be more concise. For example, our F# for Visualization library allows the user to try to visualize any value of any type using this technique to invoke custom visualization routines for different (known) types (read more).
The second is to replace your two separate types with a single type:
The third solution is to introduce a new type that unifies just those two types:
这是其中正确答案不是“这就是你如何做”,而是“不要那样做”的情况之一。如果您不打算坚持使用 F# 的习惯用法和类型检查器,那么使用 F# 就没有真正的优势。
从我的角度来看,如果您的类型非常相似,那么它们应该合并为同一类型:
除此之外,您可以将两种类型包装在另一种类型中:
另一层间接永远不会伤害任何人。但至少现在您的客户可以用另一个对象包装对象而不会失去类型安全性。
除此之外,请为不同类型公开两个单独的方法。
This is one of those cases where the correct answer isn't "here's how you do it", but instead "don't do it like that". There's no real advantage using F# if you aren't going to stick with its idioms and type-checker.
From my point of view, if your types are so similar, they should be merged into the same type:
Barring that, you can wrap your two types in another type:
Another layer of indirection never hurt anyone. But at least now your clients can wrap the objects with another without losing type safety.
And barring all of that, expose two separate methods for your different types.
这闻起来很可疑,你应该描述更大的问题背景,因为看起来你不应该处于这种情况。那是说:
This smells very fishy, you should describe the bigger problem context as it seems like you shouldn't be in this situation. That said: