F# 之旅(上)

写在前面的话

解答一下在上一篇文章《在Visual Studio中入门F#》中有人的提问,

  1. 问:是准备写 F# 系列吗?

      答:当然不是,本人也是刚刚学习 F#,只是翻译微软官方的文档,但是我会尽力翻译更多的文章。

  2. 问:你们的项目使用F#写的吗?

      答:本人大三学生,也不是什么大佬,兴趣而已。

   

在这篇文章中

  • 怎样运行示例代码
  • 函数和模块
  • 数字、布尔值和字符串
  • 元组
  • 管线和组成
  • 列表、数组和序列

学习 F# 最好的方式是读写 F# 代码。本文将介绍 F# 语言的一些主要功能,并给您一些可以在您计算机上执行的代码片段。

F# 中有两个重要概念:函数和类型。本教程将强调 F# 的这两个特点。

原文链接

Tour of F#

备注,原文较长,本人分为《F# 之旅》上下两部分翻译。

怎样运行示例代码
执行这些示例代码最快的方式是使用 F# Interactive。仅仅通过拷贝/粘贴示例代码就能运行它们了。当然,您也可以在 Visual Studio 中建立一个控制台应用程序来编译并运行它们。

函数或模块
组织在模块中的函数是任何 F# 程序最基本的部分。函数执行根据输入以生成输出的工作,并使用模块进行组织,这是在 F# 中组织事物的主要方式。函数使用“let 绑定”关键字定义, 需要一个名称并定义参数。

 1 module BasicFunctions =
 2
 3     /// 您使用“let”定义一个函数。这个函数接收一个整数类型的参数,返回一个整数。
 4     let sampleFunction1 x = x*x + 3
 5
 6     /// 使用函数,使用“let”命名函数的返回值。
 7     /// 变量的类型由函数的返回值推断而出。
 8     let result1 = sampleFunction1 4573
 9
10     // 本行使用“%d”以 int 类型格式输出结果。这是类型安全的。
11     // 如果“result1”不是“int”类型,那么该行将无法编译成功。
12     printfn "The result of squaring the integer 4573 and adding 3 is %d" result1
13
14     /// 当有需要时,可使用“(argument:type)”注明参数的类型。括号是必需的。
15     let sampleFunction2 (x:int) = 2*x*x - x/5 + 3
16
17     let result2 = sampleFunction2 (7 + 4)
18     printfn "The result of applying the 2nd sample function to (7 + 4) is %d" result2
19
20     /// 条件表达式由 if/then/elif/else 构成。
21     ///
22     /// 注意 F# 使用空格缩进语法,与 Python 相似。
23     let sampleFunction3 x =
24         if x < 100.0 then
25             2.0*x*x - x/5.0 + 3.0
26         else
27             2.0*x*x + x/5.0 - 37.0
28
29     let result3 = sampleFunction3 (6.5 + 4.5)
30
31     // 本行使用“%f”将结果以 float 类型格式输出。同上述的“%d”,这是类型安全的。
32     printfn "The result of applying the 2nd sample function to (6.5 + 4.5) is %f" result3

“let 绑定”关键字同样可以将值绑定到名称上,与其他语言中的变量类似。默认情况下,绑定后便不可变更,这意味值或函数绑定到名称上后,就不能在更改了。这与其他语言中的变量不同,它们是可变的,也就是说,它们的值可以在任何时间点上更改。如果需要可变的绑定,可以使用“let mutable”语法。

 1 module Immutability =
 2
 3     /// 使用“let”将一个值绑定到名称上,它不可改变。
 4     ///
 5     /// 代码第二行编译失败,因为“number”是不可变的。
 6     /// 重定义“number”为一个不同的值,在 F# 中是不允许的。
 7     let number = 2
 8     // let number = 3
 9
10     /// 一个可变的绑定。“otherNumber”能够改变值。
11     let mutable otherNumber = 2
12
13     printfn "‘otherNumber‘ is %d" otherNumber
14
15     // 当需要改变值时,使用“<-”分配一个新值。
16     //
17     // 注意,“=”与此不同,“=”用于判断相等。
18     otherNumber <- otherNumber + 1
19
20     printfn "‘otherNumber‘ changed to be %d" otherNumber

数字、布尔值和字符串

作为一门 .NET 语言,F# 同样支持 .NET 底层的基本类型。

下面是在 F# 中表示各种数值类型的方式:

 1 module IntegersAndNumbers =
 2
 3     /// 这是整数示例。
 4     let sampleInteger = 176
 5
 6     /// 这是浮点数示例。
 7     let sampleDouble = 4.1
 8
 9     /// 这里使用一些运算符计算得到一个新的数字。数字类型使用
10     /// “int”,“double”等函数进行转换。
11     let sampleInteger2 = (sampleInteger/4 + 5 - 7) * 4 + int sampleDouble
12
13     /// 这是一个从0到99的数字列表。
14     let sampleNumbers = [ 0 .. 99 ]
15
16     /// 这是一个由0到99的数字和它们的平方数构成的元组所形成的列表。
17     let sampleTableOfSquares = [ for i in 0 .. 99 -> (i, i*i) ]
18
19     // 接下来的一行输出这个包含许多元组的列表,使用“%A”进行泛型化输出。
20     printfn "The table of squares from 0 to 99 is:\n%A" sampleTableOfSquares

布尔值像这样执行基础的条件判断逻辑:

 1 module Booleans =
 2
 3     /// 布尔类型的值为“true”和“fales”。
 4     let boolean1 = true
 5     let boolean2 = false
 6
 7     /// 对布尔类型进行操作的运算符有“not”,“&&”和“||”。
 8     let boolean3 = not boolean1 && (boolean2 || false)
 9
10     // 本行使用“%d”输出布尔类型值。这是类型安全的。
11     printfn "The expression ‘not boolean1 && (boolean2 || false)‘ is %b" boolean3

下面介绍基本的字符串操作:

 1 module StringManipulation =
 2
 3     /// 字符串需要使用双引号。
 4     let string1 = "Hello"
 5     let string2  = "world"
 6
 7     /// 字符串同样可以使用“@”创建逐字字符串。
 8     /// 这将忽略“\”,“\n”,“\t”等转义字符。
 9     let string3 = @"C:\Program Files\"
10
11     /// 字符串文本也可以使用三重引号。
12     let string4 = """The computer said "hello world" when I told it to!"""
13
14     /// 字符串通常使用“+”运算符进行连接。
15     let helloWorld = string1 + " " + string2
16
17     // 本行使用“%d”输出一个字符串变量。这是类型安全的。
18     printfn "%s" helloWorld
19
20     /// 子字符串使用索引器表示。本行提取前7个字符作为子字符串。
21     /// 注意,与许多编程语言相同,字符串在 F# 中从0开始索引。
22     printfn "%s" substring

元组

元组在 F# 中处于很重要的位置。它是一组未命名的,但有序的值,它们整体就能当作一个值。可以将它们理解为由其他值聚合而成的值。它们有许多的用途,例如方便函数返回多个值,方便特殊值组织在一起。

 1 module Tuples =
 2
 3     /// 一个由整数构成的元组示例。tuple1 的类型是 int*int*int
 4     let tuple1 = (1, 2, 3)
 5
 6     /// 一个交换元组中两个值顺序的函数。
 7     ///
 8     /// F# 类型推断将自动泛化函数,意味着可以在任何类型下工作。
 9     let swapElems (a, b) = (b, a)
10
11     printfn "The result of swapping (1, 2) is %A" (swapElems (1,2))
12
13     /// 一个由一个整数、一个字符串和一个双精度浮点数组成的元组。tuple2 的类型是 int*string*float
14     let tuple2 = (1, "fred", 3.1415)
15
16     printfn "tuple1: %A\ttuple2: %A" tuple1 tuple2

在 F# 4.1 中,您还可以使用 struct 关键字将一个元组定义为结构体元组。这些同样可以与C# 7/Visual Basic 15 中的结构体元组进行互操作:

 1 /// 元组通常是对象,但是它们也可以表示为结构体。
 2 ///
 3 /// 它们完全可以与 C# 和 Visual Basic.NET 中的结构体元组进行互操作。
 4 /// 结构体元组不能隐式转换为对象元组 (通常称为引用元组)。
 5 ///
 6 /// 因为上述原因,下面的第二行将无法编译。
 7 let sampleStructTuple = struct (1, 2)
 8 //let thisWillNotCompile: (int*int) = struct (1, 2)
 9
10 // 您可以这样做。
11 let convertFromStructTuple (struct(a, b)) = (a, b)
12 let convertToStructTuple (a, b) = struct(a, b)
13
14 printfn "Struct Tuple: %A\nReference tuple made from the Struct Tuple: %A" sampleStructTuple (sampleStructTuple |> convertFromStructTuple)

您需要特别注意,因为结构体元组是值类型,您不能将它们隐式转换为引用元组,反之亦然。引用元组和结构体元组间必需进行显示转换。

管线和组成

管道运算符(| >, <|, | |> |>, <| | |) 和组合运算符  (>> 和 <<) 在 F# 中被广泛用于数据处理。这些运算符是函数,它们允许您以灵活的方式来创建函数的“管线”。下面的示例将带您浏览如何利用这些运算符来构建一个简单的功能管线。

 1 module PipelinesAndComposition =
 2
 3     /// 计算 x 的平方。
 4     let square x = x * x
 5
 6     /// 计算 x + 1。
 7     let addOne x = x + 1
 8
 9     /// 测试 x 是否是奇数。
10     let isOdd x = x % 2 <> 0
11
12     /// 一个包含5个数字的列表。
13     let numbers = [ 1; 2; 3; 4; 5 ]
14
15     /// 传入一个数字列表,它将筛选出偶数,
16     /// 再计算结果的平方,然后加1。
17     let squareOddValuesAndAddOne values =
18         let odds = List.filter isOdd values
19         let squares = List.map square odds
20         let result = List.map addOne squares
21         result
22
23     printfn "processing %A through ‘squareOddValuesAndAddOne‘ produces: %A" numbers (squareOddValuesAndAddOne numbers)
24
25     /// 修改 “squareOddValuesAndAddOne” 更短的方法是将每个过程中产生的中间
26     /// 结果嵌套到函数调用本身。
27     ///
28     /// 这样使函数变得更短,但是这样很难查看函数的执行顺序。
29     let squareOddValuesAndAddOneNested values =
30         List.map addOne (List.map square (List.filter isOdd values))
31
32     printfn "processing %A through ‘squareOddValuesAndAddOneNested‘ produces: %A" numbers (squareOddValuesAndAddOneNested numbers)
33
34     /// 一个更好的方式去编写 “squareOddValuesAndAddOne” 函数,那就是使用 F#
35     /// 的管道运算符。这与函数嵌套一样允许您避免创建中间结果,但仍保持较高的可读性。
36     let squareOddValuesAndAddOnePipeline values =
37         values
38         |> List.filter isOdd
39         |> List.map square
40         |> List.map addOne
41
42     printfn "processing %A through ‘squareOddValuesAndAddOnePipeline‘ produces: %A" numbers (squareOddValuesAndAddOnePipeline numbers)
43
44     /// 您可以继续精简 “squareOddValuesAndAddOnePipeline”函数,通过使用
45     /// Lamdba表达式移除第二个 “List.map”
46     ///
47     /// 注意,Lamdba表达式中也同样用到管道运算符。
48     /// can be used for single values as well.  This makes them very powerful for processing data.
49     let squareOddValuesAndAddOneShorterPipeline values =
50         values
51         |> List.filter isOdd
52         |> List.map(fun x -> x |> square |> addOne)
53
54     printfn "processing %A through ‘squareOddValuesAndAddOneShorterPipeline‘ produces: %A" numbers (squareOddValuesAndAddOneShorterPipeline numbers)
55
56     /// 最后,您可以解决需要显示地采用值作为参数的问题,通过使用“>>”来编写两个核
57     /// 心操作:筛选出偶数,然后平方和加1。同样,Lamdba表达式“fun x-> ...”也不需
58     /// 要,因为x 只是在该范围中被定义,以便将其传入函数管线。因此,“>>”可以在这
59     /// 里使用。
60     ///
61     /// “squareOddValuesAndAddOneComposition”的结果本身就是一个将整数列表作
62     /// 为其输入的函数。 如果您使用整数列表执行“squareOddValuesAndAddOneComposition”,
63     /// 则会注意到它与以前的函数相同。
64     ///
65     /// 这是使用所谓的函数组合。 这是可能的,因为F#中的函数使用Partial Application,
66     ///每个数据处理操作的输入和输出类型与我们使用的函数的签名相匹配。
67     let squareOddValuesAndAddOneComposition =
68         List.filter isOdd >> List.map (square >> addOne)
69
70     printfn "processing %A through ‘squareOddValuesAndAddOneComposition‘ produces: %A" numbers (squareOddValuesAndAddOneComposition numbers)

上述的示例使用了许多 F# 的特性,包括列表处理函数,头等函数和部分应用程序。虽然对于每个概念都有深刻的理解是较为困难的,但应该清楚的是,在使用函数管线来处理数据有多么容易。

列表、数组和序列

列表、数组和序列是 F# 核心库中3个基础的集合类型。

列表是有序的、不可变的、具有相同类型元素的集合。它们是单链表,这意味着它们是易于枚举的,但是如果它们很大,则不易于随机存取和则随机访问和级联。这与其他流行语言中的列表不同,后者通常不使用单链表来表示列表。

 1 module Lists =
 2
 3     /// 列使用“[...]”定义,这是一个空列表。
 4     let list1 = [ ]
 5
 6     /// 这是一个包含3个元素的列表,  “;”用于分割在同一行的元素。
 7     let list2 = [ 1; 2; 3 ]
 8
 9     /// 您也可以将各元素独占一行以进行分割。
10     let list3 = [
11         1
12         2
13         3
14     ]
15
16     /// 这是一个包含1到1000整数的列表。
17     let numberList = [ 1 .. 1000 ]
18
19     /// 列表可以通过计算得到,这是包含一年中所有天的列表。
20     let daysList =
21         [ for month in 1 .. 12 do
22               for day in 1 .. System.DateTime.DaysInMonth(2017, month) do
23                   yield System.DateTime(2017, month, day) ]
24
25     // 使用“List.take”输出“dayList”中的前5个元素。
26     printfn "The first 5 days of 2017 are: %A" (daysList |> List.take 5)
27
28     /// 计算中可以包含条件判断。 这是一个包含棋盘上的黑色方块的坐标元组的列表。
29     let blackSquares =
30         [ for i in 0 .. 7 do
31               for j in 0 .. 7 do
32                   if (i+j) % 2 = 1 then
33                       yield (i, j) ]
34
35     /// 列表可以使用“List.map”和其他函数式编程组合器进行转换。 此处通过使用
36     /// 使用管道运算符将参数传递给List.map,计算列表中数字的平方,产生一个新的列表。
37     let squares =
38         numberList
39         |> List.map (fun x -> x*x)
40
41     /// 还有很多其他列表组合器。如下计算能被3整除的数字的平方数。
42     let sumOfSquares =
43         numberList
44         |> List.filter (fun x -> x % 3 = 0)
45         |> List.sumBy (fun x -> x * x)
46
47     printfn "The sum of the squares of numbers up to 1000 that are divisible by 3 is: %d" sumOfSquares

数组是大小固定的、可变的、具有相同类型的元素的集合。 它们支持元素的快速随机访问,并且比F#列表更快,因为它们是连续的内存块。

 1 module Arrays =
 2
 3     /// 这是一个空数组。注意,语法与列表相似,但是数组使用的是“[| ... |]”。
 4     let array1 = [| |]
 5
 6     /// 数组使用与列表相同的方式分割元素。
 7     let array2 = [| "hello"; "world"; "and"; "hello"; "world"; "again" |]
 8
 9     /// 这是一个包含1到1000整数的数组。
10     let array3 = [| 1 .. 1000 |]
11
12     /// 这是一个只包含“hello”和“world”的数组。
13     let array4 =
14         [| for word in array2 do
15                if word.Contains("l") then
16                    yield word |]
17
18     /// 这是一个由索引初始化的数组,其中包含从0到2000的偶数。
19     let evenNumbers = Array.init 1001 (fun n -> n * 2)
20
21     /// 使用切片符号提取子数组。
22     let evenNumbersSlice = evenNumbers.[0..500]
23
24     /// 您可以使用“for”遍历数组和列表。
25     for word in array4 do
26         printfn "word: %s" word
27
28     // 您可以使用左箭头分配运算符修改数组元素的内容。
29     array2.[1] <- "WORLD!"
30
31     /// 您可以使用“Array.map”和其他函数式编程操作来转换数组。
32     /// 以下计算以“h”开头的单词的长度之和。
33     let sumOfLengthsOfWords =
34         array2
35         |> Array.filter (fun x -> x.StartsWith "h")
36         |> Array.sumBy (fun x -> x.Length)
37
38     printfn "The sum of the lengths of the words in Array 2 is: %d" sumOfLengthsOfWords

序列是一系列逻辑的元素,全部是相同的类型。 这些是比列表和数组更常用的类型,可以将其作为任何逻辑元素的“视图”。 它们脱颖而出,因为它们可以是惰性的,这意味着元素只有在需要时才被计算出来。

 1 module Sequences =
 2
 3     /// 这是一个空的队列。
 4     let seq1 = Seq.empty
 5
 6     /// 这是这是含有值的队列。
 7     let seq2 = seq { yield "hello"; yield "world"; yield "and"; yield "hello"; yield "world"; yield "again" }
 8
 9     /// 这是包含1到1000整数的队列。
10     let numbersSeq = seq { 1 .. 1000 }
11
12     /// 这是包含“hello”和“world”的队列。
13     let seq3 =
14         seq { for word in seq2 do
15                   if word.Contains("l") then
16                       yield word }
17
18     /// 这个队列包含到2000范围内的偶数。
19     let evenNumbers = Seq.init 1001 (fun n -> n * 2)
20
21     let rnd = System.Random()
22
23     /// 这是一个随机的无限序列。
24     /// 这个例子使用“yield!”返回子队列中的每个元素。
25     let rec randomWalk x =
26         seq { yield x
27               yield! randomWalk (x + rnd.NextDouble() - 0.5) }
28
29     /// 这个例子显示了随机产生的前100个元素。
30     let first100ValuesOfRandomWalk =
31         randomWalk 5.0
32         |> Seq.truncate 100
33         |> Seq.toList
34
35     printfn "First 100 elements of a random walk: %A" first100ValuesOfRandomWalk
时间: 2024-10-10 09:52:19

F# 之旅(上)的相关文章

F#之旅4 - 小实践之快排

参考文章:https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/fvsc-quicksort.html F#之旅4 - 小实践之快排 这次这篇呢,就不翻译了,因为原文确实是相当的简单.先贴一下能跑的代码: 这里贴的不是文本,如果你也想尝试一下,建议你抄一遍,或者理解之后自己写一遍.来看看都有那些要注意的点吧: 1.快排算法,这里用的递归的形式,把所有数分成三部分,[比第一个元素小的部分] [第一个元素] [比第一个元素

F#之旅0 - 开端

F#之旅0 - 开端 UWP的学习告一段落,CozyRSS的UWP版本并没有做.UWP跟wpf开发几乎一模一样,然后又引入了很多针对移动设备的东西,这部分有点像android.没啥太大的意思,不难,估计坑不少,但是暂时的没太大的欲望去玩. 学一门函数式编程语言,听起来就是一件不错的事情. 函数式编程,准确的来说应该是函数式编程这种编程范式,在很多中编程语言中都可以玩的.现在流行的js.python.lua.c++.java都有闭包了,至于惰性计算.常量,也好像不那么重要.但是既然特意说学一门函数

F#之旅8 - 图片处理应用之动画二维码

首先,先介绍下什么是动画二维码.前些天在网上闲逛,突然看到一个开源项目,发现一种二维码的新玩法.https://github.com/sylnsfar/qrcode/blob/master/README-cn.md.二维码各种美化早就有看过,原理也大概知道,一是利用二维码的容错率,二是利用识别工具的纠错能力.这次的二维码,让我有点excited,居然把动画和二维码结合起来了.当然,具体把这种二维码叫什么,我也定不了,叫动画二维码.动态二维码.gif二维码都可以吧.动画二维码和之前的美化过的二维码

F#之旅5 - 小实践之下载网页(爬虫基础库)

参考文章:https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/fvsc-download.html 参考的文章教了我们如果在F#里利用.Net的库来下载一个网页,这里,我来发散一下,把它弄成一个可以用来帮助写爬虫的基础库. 首先,下载的代码我做了几处修改: 1.去掉了回调,直接改成了保存文本到文件,注意如果是下载图片不能这样写. 2.用流来一步步调用.Net的库,并且加上了异常处理. 3.增加了一个async的异步方

F#之旅3 - F# PK C#:简单的求和

原文链接:https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/fvsc-sum-of-squares.html Comparing F# with C#: A simple sumF# PK C#:简单的求和 To see what some real F# code looks like, let's start with a simple problem: "sum the squares from 1 to N

F#之旅2 - 我有特别的学F#技巧

原文地址:https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/learning-fsharp/ Learning F#Functional programming languages need a different approach学习F#函数式编程语言需要不同的学习方法 Functional languages are very different from standard imperative languages, an

F#之旅7 - 图片处理入门

首先,隆重介绍今天的主角:ImageFactory(http://imageprocessor.org/).虽然我并没有在实际工作中用到这个库,但是它干净利索的使用方式打动了我,很久以前就存了下来.这个库的开发语言是C#,nuget有下载,提供了一系列可以链式调用的方法来处理图片.关键的一点,它是开源的,代码很清晰,有什么bug和不爽可以自己想怎么改就怎么改. 接下来,需要一提的是,我对图片处理一窍不通.所以,这篇文章是我入门图片处理的记录.当然,矩形圆形直线.RGB这类基础,就不特别提了. 一

先他会很拉场文资深上你领图参千zhi

意速場料正部隊或北團處邊空主先片有整線直革效與手能容識規許影點如周下成京資候格先當隊劃候也再幾你廣子決電外證平員般老會且治加難信按戰狀件子界更相車第持風拉那兩一邊許很的書其比因因百查子之以情消難具海張織常思始了理快作識國群新門屬第前了然院京層就派科器教按東縣條放體類形辦半持種公劃西議通復目因開麼熱標乾外進種地方北 實整正好手隊鐵證流難常越入候圓廠出火政月級說相克快便證去要數效見長非會門老種根素無適到勞果外觀知周快用常你養則此想准無節由定心資國認明天件定產斯斯深節包道叫石同色必少段七世千石解規做省

Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

一.前言 今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk 我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为了安全或者效率问题,会把一些重要的功能放到native层,那么这样一来,我们前篇说到的Eclipse调试smali源码就显得很无力了,因为核心的都在native层,Android中一般native层使用的是so库文件,所以我们这篇就来介绍如何调试so文件的内容,从而让我们破解成功率达到更高的一层.