F#学习之路(5)元组类型
元组类型,表示一组有序数据类型的集合。F#通过支持元组类型,方便了我们定义临时数据结构,而不需要为了临时的数据专门定义一个数据类型。
一、元组的定义:
let tuple_3=("F#",1.9,"F# Function Language")
在F#中元组使用小括号,元素之间逗号分隔来定义。元组元素可以是任何类型。
上面代码中,tuple_2的类型是int*int,而tuple_3的类型为string*float*string。元组类型使用元组元素类型乘号连接的方式。
元组类型可以作为函数的输入参数,也可以作为函数的返回值。
a+1,b+2,c+3
上面函数的类型为 int*int*int->int*int*int
元组类型虽然方便我们组织一组数据类型,而无需专门定义一个数据结构。但很显然他不能很好地帮助我们理解数据结构的语义,尤其是互操作时作为函数返回值的情况下。给元组类型取一个别名,能够部分缓解这个问题。
let getFSharpDesc ():FSharpDesc =
("F#",1.9,"F# Function Language")
二、元组类型的比较
在F#中元组类型可以进行比较,前提当然是同类型的元组类型。所谓同类型,是指元组的元素长度相等,元素类型相同。F#从前至后依次比较。
打印的结果 为 true
三、元组与模式匹配
模式匹配在函数式语言中经常出现,其功能强大。比命令式语言中switch强大的多。不仅可以匹配基本类型、元组类型、列表类型、记录类型,还可以用于类型检测。在F#中还允许自定义类型的模式匹配,通过活动模式(Active Pattern)可以做到这一点。
let one,two,three,four,five,six,seven=tuple_7
[one;two;three;four;five;six;] @ (match seven with t1,t2,t3 ->[t1;t2;t3] )
|> List.iteri
(fun index item ->printfn "%d,%A" index item)
元组类型不仅可以使用常用的模式匹配方法。
还可以使用隐式的模式匹配方法来分解元组
这种隐式的模式匹配大大方便了我们使用元组类型的分解。上面的代码使用了列表,列表将在下一篇博客中讨论。
四、元组类型的互操作
在学习F#函数时,我们知道F#函数的输入参数是不需要括号的。
a+b+c
let f2 (a,b,c) =
a+b+c
上面的代码中f1和f2的类型是不一样的。
f1的函数类型是int->int->int->int,而f2的函数类型是int*int*int->int。
我把f1函数称为柯里化函数风格,而把f2函数称为元组函数风格。
之所以称f1为柯里化的函数风格,是因为f1可以进行柯里化的函数调用,可以部分地传入参数值。而f2则必须全部传入。
两种函数风格的适用场景是什么了?
柯里化的函数很显然比较适合扮演高阶函数。根据我目前所理解,在不讨论互操作的情况下,元组类型风格的函数总是可以通过柯里化的函数风格代替的。在不需要互操作的情况下,应该优先使用柯里化的函数风格,因为柯里化的函数可以更好的组合,从这个角度来说,柯里化的函数风格可以很好的组合代码,而元组类型的函数风格则用来组合数据。
为什么说在互操作场景中优先使用元组类型的函数风格?
回答上面的问题,则要讨论一下元组类型真正的类型是什么?什么叫元组类型真正的类型呀,元组类型不就是元组类型吗?呵呵,绕起来了。
上面的tuple_3元组类型是int*int*int,这是对的。但这只是表面,实质上它的真实类型是Tuple<int,int,int>,F#编译器做了手脚。Tuple类型定义了一个泛型的记录类型,它有三个元素。
{ Item1: 'a; Item2: 'b; Item3: 'c }
在F#中,一共定义了六个泛型版的元组类型。从2个元素到七个元素的元组类型。很显然,元组类型最少两个元素,最多没有限制,理论上肯定是只要内存足够就可以了。F#如何处理超过七个元素的元组类型的了。根据我的测试,发现如果超过七个元素,超过的元素将变成一个嵌套的元组的成员,依此类推。举个例子:
(a,b,c,a+b,a+c,b+c,a+b+c,10*a+2*b+c)
上面的F#函数对应的c#方法签名是:
注意上面的讨论是针对函数返回值的。对于函数输入参数并不适用,我们要分别讨论。
对于函数来说,元组类型的函数风格与柯里化的函数风格,对于c#来说,并没有区别。或者说,柯时化的函数风格和元组类型的函数风格都会转化为c#方法风格。
a+b+c+d+e+f+g+h
let TestCurrying a b c d e f g h =
a+b+c+d+e+f+g+h
对应的c#方法签名:
public static int TestTuple(int a, int b, int c, int d, int e, int f, int g, int h);
一个有趣的例子:
let tuple_9=(1,2,3,4,5,6,7,8,9)
上面的tuple_7和tuple_9在F#中是完全不同的类型,但对于c#来说,却是完全等效的。
但当我们在类型上使用这两种风格的函数时就发生了变化,这种变化还很大。下面我就定义一个记录类型:
type Order =
{ID:string;Name:string;CreateDate:System.DateTime;Remark:string option}
with
static member CreateWithCurrying id name createdate remark =
{ID=id;Name=name;CreateDate=createdate;Remark=remark}
static member CreateWithTuple(id, name, createdate, remark) =
{ID=id;Name=name;CreateDate=createdate;Remark=remark}
我创建了一个记录类型,叫Order,并使用静态成员方法创建类型实例。
c#签名如下:
public static TupleTest.Order CreateWithTuple(string id, string name, DateTime createdate, Option<string> remark);
这意味着什么,这意味着当我们在F#中使用柯里化风格的函数作为公开接口时,不是c#程序员习惯的调用方式。而采用元组风格的函数与c#程序员的习惯是一致的。
在F#中元组的存在,使得接受多值的返回值时,F#比C#处理方式漂亮的多。
| true, d -> printfn "%s" (d.ToString())
| _ -> printfn "datetime parse error"
五、总结:
(1) 元组类型一般用做临时数据的容器。业务数据结构应该选用记录类型或类、结构。
(2) 当需要与其他.net语言互操作时,元组类型用做方法或函数的输入参数,有更好的兼容性,除非你的确需要柯里化的函数风格。在互操作时,元组类型不要作为函数的返回值,如果使用元组类型作为函数返回值,互操作的语言就必须引用F#的特定函数库。
(3) 元组类型与模式匹配语法相结合,可以很好的组合、拆分。