F# 之旅(上)
一、写在前面的话
解答一下在上一篇文章《在Visual Studio中入门F#》中有人的提问,
1. 问:是准备写 F# 系列吗?
答:当然不是,本人也是刚刚学习 F#,只是翻译微软官方的文档,但是我会尽力翻译更多的文章。
2. 问:你们的项目使用F#写的吗?
答:本人大三学生,也不是什么大佬,兴趣而已。
二、在这篇文章中
- 怎样运行示例代码
- 函数和模块
- 数字、布尔值和字符串
- 元组
- 管线和组成
- 列表、数组和序列
学习 F# 最好的方式是读写 F# 代码。本文将介绍 F# 语言的一些主要功能,并给您一些可以在您计算机上执行的代码片段。
F# 中有两个重要概念:函数和类型。本教程将强调 F# 的这两个特点。
三、原文链接
备注,原文较长,本人分为《F# 之旅》上下两部分翻译。
四、开始
1. 怎样运行示例代码
执行这些示例代码最快的方式是使用 F# Interactive。仅仅通过拷贝/粘贴示例代码就能运行它们了。当然,您也可以在 Visual Studio 中建立一个控制台应用程序来编译并运行它们。
2. 函数或模块
组织在模块中的函数是任何 F# 程序最基本的部分。函数执行根据输入以生成输出的工作,并使用模块进行组织,这是在 F# 中组织事物的主要方式。函数使用“let 绑定”关键字定义, 需要一个名称并定义参数。
module BasicFunctions =
/// 您使用“let”定义一个函数。这个函数接收一个整数类型的参数,返回一个整数。
let sampleFunction1 x = x*x +
/// 使用函数,使用“let”命名函数的返回值。
/// 变量的类型由函数的返回值推断而出。
let result1 = sampleFunction1
// 本行使用“%d”以 int 类型格式输出结果。这是类型安全的。
// 如果“result1”不是“int”类型,那么该行将无法编译成功。
printfn "The result of squaring the integer 4573 and adding 3 is %d" result1
/// 当有需要时,可使用“(argument:type)”注明参数的类型。括号是必需的。
let sampleFunction2 (x:int) = *x*x - x/ +
let result2 = sampleFunction2 ( + )
printfn "The result of applying the 2nd sample function to (7 + 4) is %d" result2
/// 条件表达式由 if/then/elif/else 构成。
///
/// 注意 F# 使用空格缩进语法,与 Python 相似。
let sampleFunction3 x =
if x < 100.0 then
2.0*x*x - x/5.0 + 3.0
else
2.0*x*x + x/5.0 - 37.0
let result3 = sampleFunction3 (6.5 + 4.5)
// 本行使用“%f”将结果以 float 类型格式输出。同上述的“%d”,这是类型安全的。
printfn "The result of applying the 2nd sample function to (6.5 + 4.5) is %f" result3
“let 绑定”关键字同样可以将值绑定到名称上,与其他语言中的变量类似。默认情况下,绑定后便不可变更,这意味值或函数绑定到名称上后,就不能在更改了。这与其他语言中的变量不同,它们是可变的,也就是说,它们的值可以在任何时间点上更改。如果需要可变的绑定,可以使用“let mutable”语法。
module Immutability =
/// 使用“let”将一个值绑定到名称上,它不可改变。
///
/// 代码第二行编译失败,因为“number”是不可变的。
/// 重定义“number”为一个不同的值,在 F# 中是不允许的。
let number =
// let number = 3
/// 一个可变的绑定。“otherNumber”能够改变值。
let mutable otherNumber =
printfn "'otherNumber' is %d" otherNumber
// 当需要改变值时,使用“<-”分配一个新值。
//
// 注意,“=”与此不同,“=”用于判断相等。
otherNumber <- otherNumber +
printfn "'otherNumber' changed to be %d" otherNumber
3. 数字、布尔值和字符串
作为一门 .NET 语言,F# 同样支持 .NET 底层的基本类型。
下面是在 F# 中表示各种数值类型的方式:
module IntegersAndNumbers =
/// 这是整数示例。
let sampleInteger =
/// 这是浮点数示例。
let sampleDouble = 4.1
/// 这里使用一些运算符计算得到一个新的数字。数字类型使用
/// “int”,“double”等函数进行转换。
let sampleInteger2 = (sampleInteger/ + - ) * + int sampleDouble
/// 这是一个从0到99的数字列表。
let sampleNumbers = [ .. ]
/// 这是一个由0到99的数字和它们的平方数构成的元组所形成的列表。
let sampleTableOfSquares = [ for i in .. -> (i, i*i) ]
// 接下来的一行输出这个包含许多元组的列表,使用“%A”进行泛型化输出。
printfn "The table of squares from 0 to 99 is:\n%A" sampleTableOfSquares
布尔值像这样执行基础的条件判断逻辑:
module Booleans =
/// 布尔类型的值为“true”和“fales”。
let boolean1 = true
let boolean2 = false
/// 对布尔类型进行操作的运算符有“not”,“&&”和“||”。
let boolean3 = not boolean1 && (boolean2 || false)
// 本行使用“%d”输出布尔类型值。这是类型安全的。
printfn "The expression 'not boolean1 && (boolean2 || false)' is %b" boolean3
下面介绍基本的字符串操作:
module StringManipulation =
/// 字符串需要使用双引号。
let string1 = "Hello"
let string2 = "world"
/// 字符串同样可以使用“@”创建逐字字符串。
/// 这将忽略“\”,“\n”,“\t”等转义字符。
let string3 = @"C:\Program Files\"
/// 字符串文本也可以使用三重引号。
let string4 = """The computer said "hello world" when I told it to!"""
/// 字符串通常使用“+”运算符进行连接。
let helloWorld = string1 + " " + string2
// 本行使用“%d”输出一个字符串变量。这是类型安全的。
printfn "%s" helloWorld
/// 子字符串使用索引器表示。本行提取前7个字符作为子字符串。
/// 注意,与许多编程语言相同,字符串在 F# 中从0开始索引。
printfn "%s" substring
4. 元组
元组在 F# 中处于很重要的位置。它是一组未命名的,但有序的值,它们整体就能当作一个值。可以将它们理解为由其他值聚合而成的值。它们有许多的用途,例如方便函数返回多个值,方便特殊值组织在一起。
module Tuples =
/// 一个由整数构成的元组示例。tuple1 的类型是 int*int*int
let tuple1 = (, , )
/// 一个交换元组中两个值顺序的函数。
///
/// F# 类型推断将自动泛化函数,意味着可以在任何类型下工作。
let swapElems (a, b) = (b, a)
printfn "The result of swapping (1, 2) is %A" (swapElems (,))
/// 一个由一个整数、一个字符串和一个双精度浮点数组成的元组。tuple2 的类型是 int*string*float
let tuple2 = (, "fred", 3.1415)
printfn "tuple1: %A\ttuple2: %A" tuple1 tuple2
在 F# 4.1 中,您还可以使用 struct 关键字将一个元组定义为结构体元组。这些同样可以与C# 7/Visual Basic 15 中的结构体元组进行互操作:
/// 元组通常是对象,但是它们也可以表示为结构体。
///
/// 它们完全可以与 C# 和 Visual Basic.NET 中的结构体元组进行互操作。
/// 结构体元组不能隐式转换为对象元组 (通常称为引用元组)。
///
/// 因为上述原因,下面的第二行将无法编译。
let sampleStructTuple = struct (, )
//let thisWillNotCompile: (int*int) = struct (1, 2) // 您可以这样做。
let convertFromStructTuple (struct(a, b)) = (a, b)
let convertToStructTuple (a, b) = struct(a, b) printfn "Struct Tuple: %A\nReference tuple made from the Struct Tuple: %A" sampleStructTuple (sampleStructTuple |> convertFromStructTuple)
您需要特别注意,因为结构体元组是值类型,您不能将它们隐式转换为引用元组,反之亦然。引用元组和结构体元组间必需进行显示转换。
5. 管线和组成
管道运算符(| >, <|, | |> |>, <| | |) 和组合运算符 (>> 和 <<) 在 F# 中被广泛用于数据处理。这些运算符是函数,它们允许您以灵活的方式来创建函数的“管线”。下面的示例将带您浏览如何利用这些运算符来构建一个简单的功能管线。
module PipelinesAndComposition =
/// 计算 x 的平方。
let square x = x * x
/// 计算 x + 1。
let addOne x = x +
/// 测试 x 是否是奇数。
let isOdd x = x % <>
/// 一个包含5个数字的列表。
let numbers = [ ; ; ; ; ]
/// 传入一个数字列表,它将筛选出偶数,
/// 再计算结果的平方,然后加1。
let squareOddValuesAndAddOne values =
let odds = List.filter isOdd values
let squares = List.map square odds
let result = List.map addOne squares
result
printfn "processing %A through 'squareOddValuesAndAddOne' produces: %A" numbers (squareOddValuesAndAddOne numbers)
/// 修改 “squareOddValuesAndAddOne” 更短的方法是将每个过程中产生的中间
/// 结果嵌套到函数调用本身。
///
/// 这样使函数变得更短,但是这样很难查看函数的执行顺序。
let squareOddValuesAndAddOneNested values =
List.map addOne (List.map square (List.filter isOdd values))
printfn "processing %A through 'squareOddValuesAndAddOneNested' produces: %A" numbers (squareOddValuesAndAddOneNested numbers)
/// 一个更好的方式去编写 “squareOddValuesAndAddOne” 函数,那就是使用 F#
/// 的管道运算符。这与函数嵌套一样允许您避免创建中间结果,但仍保持较高的可读性。
let squareOddValuesAndAddOnePipeline values =
values
|> List.filter isOdd
|> List.map square
|> List.map addOne
printfn "processing %A through 'squareOddValuesAndAddOnePipeline' produces: %A" numbers (squareOddValuesAndAddOnePipeline numbers)
/// 您可以继续精简 “squareOddValuesAndAddOnePipeline”函数,通过使用
/// Lamdba表达式移除第二个 “List.map”
///
/// 注意,Lamdba表达式中也同样用到管道运算符。
/// can be used for single values as well. This makes them very powerful for processing data.
let squareOddValuesAndAddOneShorterPipeline values =
values
|> List.filter isOdd
|> List.map(fun x -> x |> square |> addOne)
printfn "processing %A through 'squareOddValuesAndAddOneShorterPipeline' produces: %A" numbers (squareOddValuesAndAddOneShorterPipeline numbers)
/// 最后,您可以解决需要显示地采用值作为参数的问题,通过使用“>>”来编写两个核
/// 心操作:筛选出偶数,然后平方和加1。同样,Lamdba表达式“fun x-> ...”也不需
/// 要,因为x 只是在该范围中被定义,以便将其传入函数管线。因此,“>>”可以在这
/// 里使用。
///
/// “squareOddValuesAndAddOneComposition”的结果本身就是一个将整数列表作
/// 为其输入的函数。 如果您使用整数列表执行“squareOddValuesAndAddOneComposition”,
/// 则会注意到它与以前的函数相同。
///
/// 这是使用所谓的函数组合。 这是可能的,因为F#中的函数使用Partial Application,
///每个数据处理操作的输入和输出类型与我们使用的函数的签名相匹配。
let squareOddValuesAndAddOneComposition =
List.filter isOdd >> List.map (square >> addOne)
printfn "processing %A through 'squareOddValuesAndAddOneComposition' produces: %A" numbers (squareOddValuesAndAddOneComposition numbers)
上述的示例使用了许多 F# 的特性,包括列表处理函数,头等函数和部分应用程序。虽然对于每个概念都有深刻的理解是较为困难的,但应该清楚的是,在使用函数管线来处理数据有多么容易。
6. 列表、数组和序列
列表、数组和序列是 F# 核心库中3个基础的集合类型。
列表是有序的、不可变的、具有相同类型元素的集合。它们是单链表,这意味着它们是易于枚举的,但是如果它们很大,则不易于随机存取和则随机访问和级联。这与其他流行语言中的列表不同,后者通常不使用单链表来表示列表。
module Lists =
/// 列使用“[...]”定义,这是一个空列表。
let list1 = [ ]
/// 这是一个包含3个元素的列表, “;”用于分割在同一行的元素。
let list2 = [ ; ; ]
/// 您也可以将各元素独占一行以进行分割。
let list3 = [
]
/// 这是一个包含1到1000整数的列表。
let numberList = [ .. ]
/// 列表可以通过计算得到,这是包含一年中所有天的列表。
let daysList =
[ for month in .. do
for day in .. System.DateTime.DaysInMonth(, month) do
yield System.DateTime(, month, day) ]
// 使用“List.take”输出“dayList”中的前5个元素。
printfn "The first 5 days of 2017 are: %A" (daysList |> List.take )
/// 计算中可以包含条件判断。 这是一个包含棋盘上的黑色方块的坐标元组的列表。
let blackSquares =
[ for i in .. do
for j in .. do
if (i+j) % = then
yield (i, j) ]
/// 列表可以使用“List.map”和其他函数式编程组合器进行转换。 此处通过使用
/// 使用管道运算符将参数传递给List.map,计算列表中数字的平方,产生一个新的列表。
let squares =
numberList
|> List.map (fun x -> x*x)
/// 还有很多其他列表组合器。如下计算能被3整除的数字的平方数。
let sumOfSquares =
numberList
|> List.filter (fun x -> x % = )
|> List.sumBy (fun x -> x * x)
printfn "The sum of the squares of numbers up to 1000 that are divisible by 3 is: %d" sumOfSquares
数组是大小固定的、可变的、具有相同类型的元素的集合。 它们支持元素的快速随机访问,并且比F#列表更快,因为它们是连续的内存块。
module Arrays =
/// 这是一个空数组。注意,语法与列表相似,但是数组使用的是“[| ... |]”。
let array1 = [| |]
/// 数组使用与列表相同的方式分割元素。
let array2 = [| "hello"; "world"; "and"; "hello"; "world"; "again" |]
/// 这是一个包含1到1000整数的数组。
let array3 = [| .. |]
/// 这是一个只包含“hello”和“world”的数组。
let array4 =
[| for word in array2 do
if word.Contains("l") then
yield word |]
/// 这是一个由索引初始化的数组,其中包含从0到2000的偶数。
let evenNumbers = Array.init (fun n -> n * )
/// 使用切片符号提取子数组。
let evenNumbersSlice = evenNumbers.[..]
/// 您可以使用“for”遍历数组和列表。
for word in array4 do
printfn "word: %s" word
// 您可以使用左箭头分配运算符修改数组元素的内容。
array2.[] <- "WORLD!"
/// 您可以使用“Array.map”和其他函数式编程操作来转换数组。
/// 以下计算以“h”开头的单词的长度之和。
let sumOfLengthsOfWords =
array2
|> Array.filter (fun x -> x.StartsWith "h")
|> Array.sumBy (fun x -> x.Length)
printfn "The sum of the lengths of the words in Array 2 is: %d" sumOfLengthsOfWords
序列是一系列逻辑的元素,全部是相同的类型。 这些是比列表和数组更常用的类型,可以将其作为任何逻辑元素的“视图”。 它们脱颖而出,因为它们可以是惰性的,这意味着元素只有在需要时才被计算出来。
module Sequences =
/// 这是一个空的队列。
let seq1 = Seq.empty
/// 这是这是含有值的队列。
let seq2 = seq { yield "hello"; yield "world"; yield "and"; yield "hello"; yield "world"; yield "again" }
/// 这是包含1到1000整数的队列。
let numbersSeq = seq { .. }
/// 这是包含“hello”和“world”的队列。
let seq3 =
seq { for word in seq2 do
if word.Contains("l") then
yield word }
/// 这个队列包含到2000范围内的偶数。
let evenNumbers = Seq.init (fun n -> n * )
let rnd = System.Random()
/// 这是一个随机的无限序列。
/// 这个例子使用“yield!”返回子队列中的每个元素。
let rec randomWalk x =
seq { yield x
yield! randomWalk (x + rnd.NextDouble() - 0.5) }
/// 这个例子显示了随机产生的前100个元素。
let first100ValuesOfRandomWalk =
randomWalk 5.0
|> Seq.truncate
|> Seq.toList
printfn "First 100 elements of a random walk: %A" first100ValuesOfRandomWalk
F# 之旅(上)的更多相关文章
- F# 之旅(下)
写在前面的话 学习 F# 一定要去体会函数式编程的特点,推荐一下阮一峰的日志<函数式编程入门教程>. 在这篇文章中 递归函数 记录和可区分联合类型 模式匹配 可选类型 度量单位 类和接口 ...
- F#之旅4 - 小实践之快排
参考文章:https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/fvsc-quicksort.html F#之旅4 - 小 ...
- F#之旅0 - 开端
F#之旅0 - 开端 UWP的学习告一段落,CozyRSS的UWP版本并没有做.UWP跟wpf开发几乎一模一样,然后又引入了很多针对移动设备的东西,这部分有点像android.没啥太大的意思,不难,估 ...
- F#之旅9 - 正则表达式
今天,cozy群有个群友发了条正则,问正则匹配相关的问题.虽然他的问题用html selector去处理可能更好,但是我也再一次发现:我忘了正则怎么写的了! 忘掉正则是有原因的,这篇文章会简单记录下F ...
- F#之旅8 - 图片处理应用之动画二维码
首先,先介绍下什么是动画二维码.前些天在网上闲逛,突然看到一个开源项目,发现一种二维码的新玩法.https://github.com/sylnsfar/qrcode/blob/master/READM ...
- F#之旅5 - 小实践之下载网页(爬虫基础库)
参考文章:https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/fvsc-download.html 参考的文章教了我们如 ...
- Rsync学习之旅上
rsync 简介 什么是rsync rsync是一款开源的,快速的,多功能的,可实现全量及增量的本地或远程数据同步备份的优秀工具. 全量:将全部数据,进行传输覆盖 增量:只传输差异部分的数据 实现增量 ...
- 全能成熟稳定开源分布式存储Ceph破冰之旅-上
@ 目录 概述 定义 传统存储方式及问题 优势 生产遇到问题 架构 总体架构 组成部分 CRUSH算法 数据读写过程 CLUSTER MAP 部署 部署建议 部署版本 部署方式 Cephadm部署 前 ...
- F#之旅6 - 简单AV推荐系统
上回说到用F#来写爬虫,这只是F#学习第一阶段的第一步.最开始,就对第一阶段做了这样的安排: 1.爬虫爬取AV数据 2.数据处理和挖掘 3.数据可视化(使用ECharts) 4.推荐系统 第一步很快就 ...
随机推荐
- shiro权限控制
1.1 简介 Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Securi ...
- sass 学习
本来看了阮一峰和于江水两位老师的博客,有看了ionic自带的sass文件,原以为自己已经是很熟悉,精通了.可是我居然连ruby都不知道真实惭愧啊,辛亏看了www.sass.hk 我想这篇官方文档肯定 ...
- curl中通过json格式吧post值返回到java中遇到中文乱码的问题
首先是: curl中模拟http请求: curl -l 127.0.0.1:8080/spacobj/core/do?acid=100 -H "token:101hh" -H &q ...
- 配置远程服务器,使hyper-v能够连接网络
一般远程服务器只有一个网卡和IP,如果你要在服务器上装虚拟机,那么要使虚拟机能够连接网络,必须要创建虚拟交换机. 如果创建虚拟交换机并桥接,那么就会改变IP地址,改变IP地址,就连接不上远程服务器.造 ...
- [译]Selenium Python文档:二、初步开始
2.1.简单使用 如果已经安装好了Selenium Python,你就可以像下面这样编写Python代码来使用它了: from selenium import webdriver from selen ...
- C++获取字符cin,getchar,get,getline的区别
原创作品,转载请注明来源:http://www.cnblogs.com/shrimp-can/p/5241544.html 1.cin>> 1)最常见的是获取输入的一个字符或数字,如 in ...
- 检测Windows程序的内存和资源泄漏之原生语言环境
最近接连收到大客户的反馈,我们开发的一个软件,姑且称之为App-E吧,在项目规模特别大的情况下,长时间使用会逐渐耗尽内存,运行越来越缓慢,软件最终崩溃.由于App-E是使用混合语言开发的,主界面使用C ...
- 一个关于Python正则表达式的快速使用手册
一直在纠结自己的博客到底应该写一些什么东西,这几天发现自己的正则用的不是很熟练,于是想要写一篇关于正则表达式的博客,目的就是为了让自己以后要用而又不会的时候不至于像无头苍蝇一样到处乱撞. 有些人在碰到 ...
- 3.Redis常用命令:String
字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等.在Redis中字符串类型的Value ...
- JavaScript ES5面向对象实现一个todolist
todo-list 前言 遵守 开始 布局 设计对象 对象的属性 事件绑定 业务逻辑单元的操作 实例化对象 参考 todo-list 前言 最近阅读了JavaScript设计模式的面向对象篇,但是又苦 ...