F#:如何在不牺牲运行时性能的情况下为元组、三元组和四元组提供 fst
F# : How to provide fst for tuples, triples and quadruples without sacrifying runtime performance
对于具有 2 个元素的基本元组,我们有 fst 和 snd:
let t2 = (2, 3)
fst t2 // = 2
snd t2 // = 3
简单。现在有 3 个元素
let t3 = (2, 3, 4)
我们如何访问第三个元素? msdn 有答案(http://msdn.microsoft.com/en-us/library/dd233200.aspx):
let third (_, _, c) = c
还是很简单。但实际上是骗人的,因为我们不能在这样的三元组上使用 fst 和 snd:
fst t3
error FS0001: Type mismatch. Expecting a int * int but given a int * int * int
The tuples have differing lengths of 2 and 3
因此有挑战性的问题:我们如何在不牺牲运行时性能的情况下为元组、三元组和四元组提供函数 fst?
无效的方法:
A) 只需添加 fst(,,_)
let fst (a,_,_) = a // hides fst (a,b), so tuples dont work anymore
B) 任何反射技巧,因为性能会受到影响或复杂的函数组合会导致在 jitted 代码级别进行额外的调用。
C) 扩展方法(没有带我到任何地方,也许其他人有更好的想法)
type Tuple<'T1, 'T2, 'T3> with
member o.fst(a,b,c) = a
static member fst(a,b,c) = a
除了具有其他签名 (t3.fst()) 之外,我什至无法获得使用元组的扩展,尽管它们是 类(不是结构)。
D)
let fst = function
| (a,b) -> a
| (a,b,c) -> a // FS0001: Type mismatch
这个问题是按原样提出的,不是为了解决问题。实际上我认为答案是不可能提供对元组进行操作的通用函数。
这是一个很酷的问题。
我认为问题在于 F# 文档暗示它编译为 Tuple<...> 对象之一。所以你必须问问自己,类型 Tuple<T1, T2>
和 Tuple<T1, T2, T3>
之间的关系是什么?答:除了名字什么都没有。完全没有关系。
所以您可以使用上面的库,或者您可以尝试自己创建包含关系的库。
可能有更好的方法来做到这一点,但这是直截了当的:
type XTuple1<'a>(a: 'a) =
member public this.fst = a
abstract member strContents: unit->string
default this.strContents() = this.fst.ToString()
override this.ToString() = "(" + this.strContents() + ")"
type XTuple2<'a, 'b>(a:'a, b:'b) =
inherit XTuple1<'a>(a)
member public this.snd = b
override this.strContents() = base.strContents() + ", " + b.ToString()
type XTuple3<'a, 'b, 'c>(a:'a, b:'b, c:'c) =
inherit XTuple2<'a, 'b>(a, b)
member public this.thrd = c
override this.strContents() = base.strContents() + ", " + c.ToString()
let inline xfst<'a> (xt:XTuple1<'a>) = xt.fst
let inline xsnd<'a,'b> (xt:XTuple2<'a,'b>) = xt.snd
let inline xthrd<'a,'b,'c> (xt:XTuple3<'a,'b,'c>) = xt.thrd
[<EntryPoint>]
let main argv =
let t3 = XTuple3(1, 'a', "word")
let a = xfst t3 // strongly typed: a is an int
let b = xsnd t3
let c = xthrd t3
printfn "%s" (t3.ToString()) // -> "(1, a, word)"
0 // return an integer exit code
正如其他人已经提到的那样,有(或多或少复杂和丑陋的)方法可以做到这一点,但我认为这里应该说的重要一点是,如果你很好地使用 F#,你 不需要这个.
如果您需要一个可以包含两个、三个或四个元素的数据结构,那么您应该使用列表而不是使用元组(在其他语言中,元组有时用于此,但在 F# 中不使用)。
如果您在 F# 中使用元组,那么您通常会使用少量元素(两个或三个),然后使用模式匹配分解它们更具可读性。比较以下内容:
// Pattern matching makes you name things, so this is quite clear
// (we don't need the third element, so we're ignoring it)
let width, height, _ = tuple
width * height
// If you had fst3 and snd3, then you could write it using just
// a single line, but the code becomes hard to follow
(fst3 tuple) * (snd3 tuple)
所以,我认为您根本不应该这样做。现在,如果您使用更复杂的数据结构(具有更多元素),那么最好使用记录之类的东西。如果您的第一个元素始终是 "name" 并且您希望具有访问名称的函数,则可以使用接口(或可区分的联合和模式匹配)。
type INamed =
abstract Name : string
type Person =
{ SomeName : string }
interface INamed with
member this.Name = this.SomeName
type FancyPerson =
{ SomeName : string; Title : string }
interface INamed with
member this.Name = this.SomeName
正确的方法取决于您在做什么。但无论如何,很可能有更好的方式来表达您的需求。
使用 this technique 方法重载结合内联函数在运行时完全没有损失,因为重载解析发生在编译时并且 overloaded 函数是在调用站点内联。
在库中有更复杂的方法来组织这些通用元组函数,@MauricioScheffer 已经发布了一些链接,显示了使用基本相同技术的更多实验。
这是一个非常短的独立代码片段,用于 重载 函数 fst
以使其与其他元组大小一起工作:
type Fst = Fst with
static member ($) (Fst, (x1,_)) = x1
static member ($) (Fst, (x1,_,_)) = x1
static member ($) (Fst, (x1,_,_,_)) = x1
// more overloads
let inline fst x = Fst $ x
对于具有 2 个元素的基本元组,我们有 fst 和 snd:
let t2 = (2, 3)
fst t2 // = 2
snd t2 // = 3
简单。现在有 3 个元素
let t3 = (2, 3, 4)
我们如何访问第三个元素? msdn 有答案(http://msdn.microsoft.com/en-us/library/dd233200.aspx):
let third (_, _, c) = c
还是很简单。但实际上是骗人的,因为我们不能在这样的三元组上使用 fst 和 snd:
fst t3
error FS0001: Type mismatch. Expecting a int * int but given a int * int * int
The tuples have differing lengths of 2 and 3
因此有挑战性的问题:我们如何在不牺牲运行时性能的情况下为元组、三元组和四元组提供函数 fst?
无效的方法:
A) 只需添加 fst(,,_)
let fst (a,_,_) = a // hides fst (a,b), so tuples dont work anymore
B) 任何反射技巧,因为性能会受到影响或复杂的函数组合会导致在 jitted 代码级别进行额外的调用。
C) 扩展方法(没有带我到任何地方,也许其他人有更好的想法)
type Tuple<'T1, 'T2, 'T3> with
member o.fst(a,b,c) = a
static member fst(a,b,c) = a
除了具有其他签名 (t3.fst()) 之外,我什至无法获得使用元组的扩展,尽管它们是 类(不是结构)。
D)
let fst = function
| (a,b) -> a
| (a,b,c) -> a // FS0001: Type mismatch
这个问题是按原样提出的,不是为了解决问题。实际上我认为答案是不可能提供对元组进行操作的通用函数。
这是一个很酷的问题。
我认为问题在于 F# 文档暗示它编译为 Tuple<...> 对象之一。所以你必须问问自己,类型 Tuple<T1, T2>
和 Tuple<T1, T2, T3>
之间的关系是什么?答:除了名字什么都没有。完全没有关系。
所以您可以使用上面的库,或者您可以尝试自己创建包含关系的库。 可能有更好的方法来做到这一点,但这是直截了当的:
type XTuple1<'a>(a: 'a) =
member public this.fst = a
abstract member strContents: unit->string
default this.strContents() = this.fst.ToString()
override this.ToString() = "(" + this.strContents() + ")"
type XTuple2<'a, 'b>(a:'a, b:'b) =
inherit XTuple1<'a>(a)
member public this.snd = b
override this.strContents() = base.strContents() + ", " + b.ToString()
type XTuple3<'a, 'b, 'c>(a:'a, b:'b, c:'c) =
inherit XTuple2<'a, 'b>(a, b)
member public this.thrd = c
override this.strContents() = base.strContents() + ", " + c.ToString()
let inline xfst<'a> (xt:XTuple1<'a>) = xt.fst
let inline xsnd<'a,'b> (xt:XTuple2<'a,'b>) = xt.snd
let inline xthrd<'a,'b,'c> (xt:XTuple3<'a,'b,'c>) = xt.thrd
[<EntryPoint>]
let main argv =
let t3 = XTuple3(1, 'a', "word")
let a = xfst t3 // strongly typed: a is an int
let b = xsnd t3
let c = xthrd t3
printfn "%s" (t3.ToString()) // -> "(1, a, word)"
0 // return an integer exit code
正如其他人已经提到的那样,有(或多或少复杂和丑陋的)方法可以做到这一点,但我认为这里应该说的重要一点是,如果你很好地使用 F#,你 不需要这个.
如果您需要一个可以包含两个、三个或四个元素的数据结构,那么您应该使用列表而不是使用元组(在其他语言中,元组有时用于此,但在 F# 中不使用)。
如果您在 F# 中使用元组,那么您通常会使用少量元素(两个或三个),然后使用模式匹配分解它们更具可读性。比较以下内容:
// Pattern matching makes you name things, so this is quite clear
// (we don't need the third element, so we're ignoring it)
let width, height, _ = tuple
width * height
// If you had fst3 and snd3, then you could write it using just
// a single line, but the code becomes hard to follow
(fst3 tuple) * (snd3 tuple)
所以,我认为您根本不应该这样做。现在,如果您使用更复杂的数据结构(具有更多元素),那么最好使用记录之类的东西。如果您的第一个元素始终是 "name" 并且您希望具有访问名称的函数,则可以使用接口(或可区分的联合和模式匹配)。
type INamed =
abstract Name : string
type Person =
{ SomeName : string }
interface INamed with
member this.Name = this.SomeName
type FancyPerson =
{ SomeName : string; Title : string }
interface INamed with
member this.Name = this.SomeName
正确的方法取决于您在做什么。但无论如何,很可能有更好的方式来表达您的需求。
使用 this technique 方法重载结合内联函数在运行时完全没有损失,因为重载解析发生在编译时并且 overloaded 函数是在调用站点内联。
在库中有更复杂的方法来组织这些通用元组函数,@MauricioScheffer 已经发布了一些链接,显示了使用基本相同技术的更多实验。
这是一个非常短的独立代码片段,用于 重载 函数 fst
以使其与其他元组大小一起工作:
type Fst = Fst with
static member ($) (Fst, (x1,_)) = x1
static member ($) (Fst, (x1,_,_)) = x1
static member ($) (Fst, (x1,_,_,_)) = x1
// more overloads
let inline fst x = Fst $ x