F#第二章试译

第二章

基本概念

 

 

 

 

 

 

 

在第一章中,你已经可以编写你的第一个F#程序。在这里,我会慢慢让你加深对你所学的东西体会,但是,至今为止,绝大多数的代码依旧是很神秘的。在这章,我将会讲解读懂这些代码应有的基础知识,但是,更重要的是,我将会在这章里面提出几个或更多典型的例子来让你在接触更加复杂的代码之前掌握F#的根基。

 

本章的第一个部分将涉及到F#中的基础类型,比如int、string 等在F#程序中的基本内建类型。然后,我会简单的介绍函数,因此你将会知道如何操纵和改变数据。

 

第三部分将详细介绍一些内建类型(functional types),例如list、option、unit。当你掌握如何使用他们以后,你能够在接下来的章节中,更好的理解F#的面向对象和函数式编程风格。

 

在本章的最后,你将能够独立的编写简单的处理数据的F#程序。当然,在以后的章节中,你能学习到更加强大和富有表现力的方式来编写你的代码,但是在此之前,让我们脚踏实地得学会这些基础。

 

基础类型

类型是一种抽象的强制类型安全的实例。类型代表了一系列的可以真实存在的实例。有些只是简单的代表了一个整数,但是其他的一些可能会更加抽象得多,比如一个函数。F#是静态类型,这意味着在编译期间,就完成了所有的类型检测。例如,你会在给一个接纳一个整数作为参数的函数传入一个字符串的时候得到一个编译错误。

 

同C#和VB.NET一样,F#支持.NET中的所有基本类型以及.NET中所允许的类型转换(这点和绝大部分的语言是保持一致的)。他们是F#的内在组成部分,并且和用户自定义的类型相互区别。

 

我们一般用let来绑定一个变量。

 

当然,你也可以让let绑定许多其他的东西,但是我们讲在第三章才讲述那些东西。现在,我们只需知道,你可以用let来绑定(通过关键字)声明一个新的标识符。例如,接下来的代码中,我们在FSI中定义了一个叫做x的变量:

 

    >let x = 1;;

    valx : int = 1

 

基本数值类型

基本数值类型有2种:整数和浮点数。整型的表示范围随着他长度而改变,因此,一些类型只能用较少的内存表示一个较小范围内的输。整数通过他们是否能够表示一个负数又分成了2类:有符号数和无符号数。

 

浮点数的表示范围也和长度有关,我们通过他们的字母后缀来确定let绑定一个整数还是浮点数。Table 2-1 给出了所有的整型和浮点型以及他们的字母后缀。

 

>let answerToEverything = 32UL;;

valeverything: uint64 = 42UL

 

>let pi = 3.1415926M;;

valpi : decimal = 3.1415926M

 

>let avogadro = 6.022e-23;;

valavogadro : float = 6.022e-23

 

 

Table 2-1. Numerical primitives in F#

Type              Suffix        .NET type             Range        

byte             uy             System.Byte          0 to 255

sbyte             y              System.SByte         -128 to 127

int16             s              System.Int16         -32768 to 32767

uint16            us             System.UInt16        0 to 65535

int, int32                     System.Int32          -231 to231-1

uint, uint32     u              System.UInt32        0 to 232-1

int64             L              System.Int64          -263 to 263-1

uint64           UL             System.UInt64        0 to 264-1

float                       System.Double                     A double-precision floating point based onthe IEEE64 standard. Rep-

                                                    resents values with approximately 15 significant digits.

 

float32          f             System.Float                       A single-precision floating pointbased on the IEEE32 standard. Rep-

                                                    resents values with approximately 7 significant digits.

 

decima           M            System.Decimal                     A fixed-precision floating-point typewith precisely 28 digits of

                                                    precision.

 

 

 

F#同样允许你通过加上0x, 0O, 0b后缀来设置16进制,8进制和二进制的值:

>let hex = 0xFCAF;;

valhex : int = 64687

 

>let oct = 0o7771L;;

valoct : int64 = 4089L

 

>let bin = 0b00101010y;;

valbin : sbyte = 42y

 

> (hex, oct, bin);;

val it : int * int64 * sbyte = (64687, 4089, 42)

 

如果对IEEE32或者是IEEE64标准很熟悉的话,你同样可以用16进制,8进制或者是2进制的方式来指定浮点数的值。F#能把二进制的值转化为他表示的对应的浮点数的值。我们通过使用不同的后缀来区分不同的精度的浮点数类型,LF后缀用来指定float类型,而lf用来指定float32类型。

 

      > 0x401E000000000000LF;;

      val it : float = 7.5

 

      > 0x00000000lf;;

      val it : float32 = 0.0f

 

 

运算

你可以标准的基本运算符。Table2-3列出了所支持的所有的运算符。和所有基于CLR的语言一样,整数做除法运算的时候结果会舍弃余数。

 

Table 2-2. Arithmetic operators

 

  Operator   Description      Example          Result

  +            Addition           1 + 2               3

  −            Subtraction        1− 2              −1

  *            Multiplication    2 *3               6

  /            Division           8L / 3L            2L

  **           Powera             2.0 ** 8.0        256.0

  %            Modulus            7 % 3                1

Power,**运算符,只能在float或者是float32类型下工作。要得到一个整数的乘方,你必须选择把他转换沉一个浮点数或者是有pown函数

 

默认情况下,运算符并不会检测运算结果是否越界,因此当你超过一个整数的表示范围的时候,他会溢出成一个负数。(同理,减法会获得一个正数当一个无法被存储在整型中的数存放在整型中的时候。)

 

      > 32767s + 1s;;

      val it : int16 = −32768s

 

      > −32768s + −1s;;

      val it : int16 = 32767s

 

当一个整数涉及到溢出的时候,你应该考虑一个表示范围更大的类型或者是使用带有检测功能的运算符,这部分内容,参考第七章

 

F#毫无疑问提供了所有你期待有的数学函数,所有的这些,都在Table2-3中列出了。

 

Table2-3.Commonmath functions

Routine     Description                           Example             Result

abs         Absolute value of a number          abs −1.0            1.0

ceil        Roundup to the nearest integer    ceil9.1            10

exp         Raisea value to a power of e       exp1.0             2.718

floor       Rounddown to the nearest integer  floor9.9           9.0

sign        Sign of the value                     sign −5              −1

log         Natural logarithm                     log 2.71828         1.0

log10       Logarithm in base 10                 log10 1000.0        3.0

sqrt        Square root                            sqrt 4.0             2.0

cos         Cosine                                  cos 0.0              1.0

sin         Sine                                     sin 0.0              0.0

tan        Tangent                                 tan1.0              1.557

pown        Compute the power of an integer    pown 2L 10  

 

 

类型转换

F#的一个原则就是不允许任何的隐式转换。这就意味着,编译器不会自动把基本数据类型转换成你所想要的,例如,把一个int16转换为int64。这将避免很多难以察觉的bug。为了打到类型转换的效果,你必须使用Table2-4中的类型转换函数。所有的类型转换函数都能接纳任意基本类型,包括字符串和字符。

 

Table2-4.Numericprimitive conversion routines

 

  Routine            Description                  Example             Result

  sbyte             Converts data to an sybte  sbyte −5            −5y

  byte               Converts data to a byte     byte "42"           42uy

  int16              Converts data to an int16   int16 'a'           97s

  uint16            Converts data to auint16   uint16 5            5us

  int32, int        Convertsdata to an int     int 2.5             2

  uint32, uint      Converts data to a uint     uint32 0xFF         255

  int64              Converts data to an int64   int64−8            −8L

  uint64             Converts data to a uint64   uint64 "0xFF"       255UL

  float              Converts data to a float     float3.1415M       3.1415

  float32            Converts data to a float32  float32 8y          8.0f

  decimal            Converts data to a decimal  decimal 1.23        1.23M

 

当上述的类型转换接受字符串为参数的时候,他们使用底层的System.Convert系列函数分析字符串的内容,这意味着,非法的输入将导致他们抛出System.FormatException异常。

 

 

大数类型

F#提供了BigInt类型来代表你要处理大于264的大整数。其实,BigInt只是System.Numer.ics.BigInteger类型的一个别名而已,他同C#和VB.NET中支持大数的语法是一模一样的。

 

BigInt使用I作为字母后缀标志。Example2-1定义了一些使用BigInt的数据。

 

>open System.Numerics

 

//Data storage units

letmegabyte  = 1024I        * 1024I

letgigabyte  = megabyte    * 1024I

letterabyte  = gigabyte    * 1024I

letpetabyte  = terabyte    * 1024I

letexabyte      = petabyte * 1024I

letzettabyte = exabyte     * 1024I;;

 

valmegabyte : BigInteger = 1048576

valgigabyte : BigInteger = 1073741824

valterabyte  : BigInteger = 1099511627776

valpetabyte  : BigInteger = 1125899906842624

valexabyte   : BigInteger = 1152921504606846976

valzettabyte : BigInteger = 1180591620717411303424

 

尽管BigInt在性能上进行了很大程度上的优化,但是他任然比使用普通的基本整型的速度慢得多。

 

 

比特运算符

基本整数类型支持比特运算符以用来操纵二进制级别的数据运算。他们经常在读写二进制数据的时候用到。参考Table2-5

 

Table2-5. Bitwise operators

 

  Operator   Description          Example                   Result

  &&&         Bitwise ‘And’       0b1111 &&& 0b0011        0b0011

  |||        Bitwise ‘Or’        0xFF00 |||0x00FF        0xFFFF

  ^^^        Bitwise ‘Exclusive Or’ 0b0011 ^^^ 0b0101   0b0110

  <<<         Left Shift          0b0001 <<< 3               0b1000

  >>>         Right Shift         0b1000 >>> 3               0b0001s

 

 

 

字符型

由于.NET平台是基于Unicode的,因此字符都是使用2个字节的UTF-16字符表示法。你可以通过给任意一个Unicode字符加上单引号来定义一个字符类型变量。当然了,字符型变量也可以用16进制字符的Unicode码来指定。

 

接下来的这段代码定义了一个字符列表,然后输出一个字符对应的16进制的值:

 

      > let vowels = ['a'; 'e'; 'i'; 'o';'u'];;

     val vowels : char list = ['a'; 'e'; 'i';'o'; 'u']

 

      > printfn "Hex u0061 = '%c'"'\u0061';;

      Hex u0061 = 'a'

     val it : unit = ()

 

为了表示那些特殊的转义字符,你必须如同Table2-6那样使用转义字符。一个转义字符是一个反斜杠加上一个要表示的特定的字符。

 

Table2-6.Character escape sequences

Character                     Meaning

\’                             Single quote        单引号

\”                            Double quote        双引号

\\                            Backslash            反斜杠

\b                            Backspace            退格符

\n                            Newline              换行

\r                            Carriage return     回车

\t                            Horizontal tab      tab

 

 

如果你想要知道一个.NET Unicode字符对应的数值,你可以通过我们在前面的Table2-3中所列出的转换方法来获得。当然,您也可以选择通过在一个字符后面加上一个后缀B来获得:

 

      >// Convert value of 'C' to an integer

      int'C';;

      val it : int = 67

      > // Convert value of 'C' to a byte

      'C'B;;

      val it : byte = 67uy

 

 

 

字符串

字符串是通过一串在双引号的的字符来定义的,可以是一行,也可以是多行。可以通过向字符串标识符后面加上.[]并向[]里传入一个非零的有符号整数来获得指定的字符:

 

 

      > let password ="abracadabra";;

 

      valpassword : string = "abracadabra"

 

      > let multiline = "This string

      takes up

      multiple lines";;

 

      val multiline : string = "Thisstring

      takes up

      multiple lines";;

 

      > multiline.[0];;

      val it : char = 'T'

      > multiline.[1];;

      val it : char = 'h'

      > multiline.[2];;

      val it : char = 'i'

      > multiline.[3];;

      valit : char = 's'

 

你可以通过在每行的最后一个字符后面添加反斜杠\ 来将一个字符串分割到好几行。当一行的最后一个字符后面是反斜杠的时候,字符串会自动忽略这一行后所有的空白符并且自动添加下一行的字符到当前字符串中去:

 

 

      let longString = "abc-\

                                 def-\

                      ghi";;

 

      > longString;;

     val it : string = "abc-def-ghi"

 

 

你可以通过在一个字符串中自由的使用类似与\t或者是\\这样的转义字符,但是,转义字符会在定义一些路径和注册表键值的时候带来很大的麻烦。你可以选择在定义一个字符串的时候加上@这个符号,这表示在这个字符串里面,不对任何字符进行转义:

 

      > let normalString ="Normal.\n.\n.\t.\t.String";;

     val normalString : string = "Normal.

      .

      .       .         .String

      > let verbatimString =@"Verbatim.\n.\n.\t.\t.String";;

     val verbatimString : string ="Verbatim.\n.\n.\t.\t.String"

 

 

和在一个字符后面加上后缀B一样,在一个字符串后面加上后缀B会通过一个字符数组返回这个字符串中所有字符。(数组将在第四张被介绍。)

 

      > let hello = "Hello"B;;

     val hello : byte array = [|72uy; 101uy;108uy; 108uy; 111uy|]

 

布尔值

F#提供了bool类型(System.Boolean)来处理只有true和false的值的情况。标准的布尔值操作符在Table2-7中被列出

 

Table2-7. Boolean operators

 

  Operator   Description    Example             Result

  &&         Boolean ‘And’  true && false       false

  ||        Boolean ‘Or’   true || false       true

  not       Boolean ‘Not’  not false            true

 

 

Example2-2提供了一张打印布尔值函数运算结果的真值表。他通过定义一个接纳一个函数名为f作为参数的printTruthTable函数来实现这一切。这个函数在真值表中的每个表格中都被调用一次,并且打印他自己的结果。接下来,我们会看到运算符&&和||被当作参数传到printTruthTable函数中去。

 

Example2-2. Printing truth tables

 

>// Print the truth table for the given function

letprintTruthTable f =

     printfn "          |true     | false |"

     printfn "          +-------+-------+"

     printfn "  true   |   %5b |  %5b   |" (f truetrue)(f true false)

     printfn "  false  |   %5b |  %5b   |" (f false true) (f false false)

     printfn "          +-------+-------+"

     printfn ""

     ();;

 

valprintTruthTable : (bool -> bool -> bool) -> unit

 

>printTruthTable (&&);;

            |  true  | false |

            +-------+-------+

 true      |  true| false  |

 false     | false | false |

            +-------+-------+

 

valit : unit = ()

>printTruthTable (||);;

            |  true  | false |

            +-------+-------+

 true      | true |  true  |

 false     | true | false |

            +-------+-------+

 

valit : unit = ()

 

F#在求布尔值表达式的时候采用了短路法则,这意味着整个表达式的值可能在第一个表达式被求出来以后就被确定了,第二个表达式不会被计算。例如:

        true || f()

结果将会是true,不会执行函数f。下面一个例子:

        false && g()

结果将会是false,不会执行函数g。

 

 

 

比较和相等

你可以通过使用Table2-8中列出的大于,小于,等于操作符来比较2个数值的大小。所有的比较运算符的执行结果都是布尔值;比较函数返回的是-1,0或者是1的依据是第一个参数是否小于,等于或者是大于第二个参数。

 

 

Table2-8. Comparison operators

  Operator    Description            Example            Result

  <           Less than               1 < 2               true

  <=          Less than or equal    4.0 <= 4.0         true

  >           Greater than            1.4e3 > 1.0e2     true

  >=          Greater than or equal 0I >=2I           false

  =           Equal to                 "abc" = "abc"     true

  <>           Not equal to            'a' <> 'b'         true

  compare    Compare two values     compare31 31     0

    在.NET中,相等是一个很难纠缠的主题。既有值相等,也有引用对象相等。对于数值类型,比较意味着变量的值是一样的,例如1=1,。但是对于引用类型,是否相等是通过重载Sytem.Object的Equals方法来判定的。更多的关于相等的细节,将会在119页的”相等对象”中介绍。

 

 

函数

至今为止,我们已经掌握在所有的F#中的基本类型,接下来,让我们定义函数来使用他们。

 

你可以使用同定义变量一样的方式定义函数,在函数名以后的所有东西都被认为是这个函数的参数。下面定义了一个接纳一个整型参数x的square函数,他将返回参数x的平方:

 

     > let square x = x * x;;

     val square : int -> int

     > square 4;;

     val it : int = 16

 

和C#不同的是,F#中没有return这个关键字。因此当你定义一个函数的时候,最后一个表达式就等于是函数执行结果的返回。

 

让我们来试着写一个让函数的输入加1的函数:

    > let addOne x = x + 1;;

valaddOne : int -> int

 

在FSI上的输出显示了这个函数的类型是int -> int。我们这样读:一个函数传入一个整数并且返回一个整数。这个规则在一个函数拥有多个参数的时候会显得稍显复杂:

    > let add x y = x + x;;

    val add : int –> int -> int

 

一个很有挑战性的读法, 类型 int -> int ->int 被读作:一个函数传入一个整数,该函数返回一个传入一个整数并且返回一个整数的函数。不要所谓的一个函数返回一个函数所迷惑和害怕。你需要知道的仅仅是如何去调用一个函数,只需要简单的把参数用空格隔开:

    > add 1 2;;

    valint : 3

 

 

类型引用

因为F#是静态类型,因此当你在给add方法传递浮点数作为参数的时候会得到一个编译错误:

    > add 1.0 2.0;;

       add 1.0 2.0;;

       ----^^^^

    stdin(3,5): error FS0001: This expression has type

               float

     but is here used withtype

               int.

 

 

你可能会对此感到很迷惑,为什么编译器就认为函数只能接纳整数作为参数呢?但是运算符+却同样可以工作在浮点数下。

 

这是因为类型引用,和C#不同,F#编译器不需要你明确的告诉编译器一个函数的所有参数类型。编译器会指定他们为通常的选择。

 

    小心,不要混淆了类型引用和动态类型。尽管F#允许你在编写代码的时候省略类型,但是这不意味着编译器在编译时不会强制检查类型。

 

 

因为运算符+为很多的类型服务,例如:byte,int,decimal,编译器会在没有附加条件的情况下简单的认为类型就是int。

 

 

posted @ 2010-11-28 12:02  Dickhead  阅读(320)  评论(0编辑  收藏  举报