F#:如何在不牺牲运行时性能的情况下为元组,三元组和四元组提供fst

时间:2015-01-13 14:19:53

标签: f# tuples inline

对于包含2个元素的基本元组,我们有fst和snd:

let t2 = (2, 3)
fst t2 // = 2
snd t2 // = 3

易。现在有3个元素

let t3 = (2, 3, 4)

我们如何访问第3个元素? 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())之外,我甚至无法使用Tuples进行扩展,尽管它们是类(不是结构)。

d)

let fst = function 
          | (a,b) -> a
          | (a,b,c) -> a // FS0001: Type mismatch

这个问题意味着它的制定,而不是解决方法。实际上我相信答案是不可能提供在元组上运行的常用函数。

3 个答案:

答案 0 :(得分:9)

使用this technique方法重载与内联函数相结合在运行时完全不受惩罚,因为重载解析发生在编译时,并且重载函数在调用站点内联

在库中组织这些通用元组函数有更复杂的方法,@ MaicicioScheffer已经发布了一些链接,显示了更多使用基本相同技术的实验。

这是一个非常短的独立代码片段,用于重载函数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

答案 1 :(得分:5)

正如其他人已经提到的那样,有(或多或少精心设计和丑陋)的方法可以做到这一点,但我认为这里应该说的重要一点是,如果你正在使用F#,你 don不需要这个

如果你需要一个可以包含两个,三个或四个元素的数据结构,那么你应该使用一个列表而不是使用元组(在其他语言中,元组有时用于此,而不是在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)

所以,我认为你不应该这样做。现在,如果您使用更复杂的数据结构(具有更多元素),那么最好使用类似记录的东西。如果您的第一个元素始终是“名称”,并且您希望具有访问该名称的功能,则可以使用接口(或区分联合和模式匹配)。

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

正确的方法取决于你正在做什么。但无论如何,最有可能更好地表达你需要的东西。

答案 2 :(得分:1)

这是一个很酷的问题。 我认为问题在于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