学习笔记-涛讲F#(基础)
简介
F# 语言是面向 .NET 的多范例编程语言。 F# 支持函数式、命令式、面向对象的编程模式。
新建一个“Hello world”项目,代码如下:
// Learn more about F# at http://docs.microsoft.com/dotnet/fsharp
open System
// Define a function to construct a message to print
let from whom =
sprintf "from %s" whom
[<EntryPoint>]
let main argv =
let message = from "F#" // Call the function
printfn "Hello world %s" message
0 // return an integer exit code
注意以下几点:
- open 导入声明:导入声明指定模块或命名空间,无需使用完全限定的名称即可引用其中的元素,参考 导入声明:open 关键字。
- let 绑定:绑定可将标识符与值或函数相关联,let 关键字用于将名称绑定到值或函数,参考 let 绑定。
- [<EntryPoint>]:显式指定main入口函数(显式入口点),没有显示指定入口点时代码会从第一行执行到最后一行(隐式入口点),参考 控制台应用程序。
类型推导
F#会自动推导类型,也支持人工指定类型,代码如下:
//类型推导
let message = "F#"
//指定类型
let (message:string) = "F#"
//指定错误类型-报错
let (message:int) = "F#"
多个输入参数的函数
函数有多个输入参数时,直接用空格分隔放在函数名后面,代码如下:
open System
//小孩票价三块,大人票价5块,求总价
let familyCost child adult =
let result =child*3+adult*5
result
[<EntryPoint>]
let main argv =
let cost = familyCost 2 2
printfn $"total cost = {cost}"
0 // return an integer exit code
注:如果参数添加括号,说明该组输入参数是元组。
定义单位
上面的familyCost函数调用时需要看函数定义才知道输入参数的含义,如果给每个参数定义一个单位代码就清晰多了,代码如下:
open System
//小孩票价三块,大人票价5块,求总价
let familyCost child adult =
let result =child*3+adult*5
result
[<Measure>]type 元
[<Measure>]type 小孩
[<Measure>]type 大人
let kidPrice=3<元/小孩>
let adultPrice=5<元/大人>
let familyCost2 (child:int<小孩>) (adult:int<大人>) =
let result =child*kidPrice+adult*adultPrice
result
[<EntryPoint>]
let main argv =
let cost = familyCost 2 2
printfn $"total cost = {cost}"
let cost2 = familyCost2 2<小孩> 2<大人>
printfn $"total cost = {cost2}"
0 // return an integer exit code
注:Measure 用于编译时单位检查,并不会生成在最终的代码中,不会影响性能也无法通过反射查看到。
Measure 的定义可以参考 度量单位,它的语法如下:
[<Measure>] type unit-name [ = measure ]
分别定义两个单位和它们之间的转换关系,代码如下:
//定义度量值 cm(厘米)
[<Measure>] type cm
//将度量值 ml(毫升)定义为立方厘米 (cm^3)
[<Measure>] type ml = cm^3
- 在涉及单位的公式中,支持整数幂(正和负)
- 单位之间的空格指示两个单位的积,* 也指示单位的积,/ 指示单位的商
- 对于倒数单位,可以使用负整数幂或 /(指示单位公式的分子与分母之间的分隔), 分母中的多个单位应括在括号中
- / 后用空格分隔的单位解释为分母的一部分,但跟踪 * 后的任何单位都解释为分子的一部分,如单位公式 kg m s^-2 和 m /s s * kg 都会转换为 kg m/s^2
两个单位涉及到数值转换的可以使用以下方式,代码如下:
//定义度量值 g(克)
[<Measure>] type g
//定义度量值 kg(千克)
[<Measure>] type kg
//定义转换常数,g kg^-1 与 g/kg 没有任何区别
let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg>
//定义转换函数
let convertGramsToKilograms (x : float<g>) = x / gramsPerKilogram
偏函数
偏函数是对原始函数的二次封装,将现有函数的部分参数预先绑定为指定值,从而得到一个新的函数,该函数就称为偏函数。
偏函数比原函数具有更少的可变参数,降低了函数调用的难度。
柯里化与偏函数并不完全等同,两者的区别可以参考 函数柯里化与偏函数。
一个简单的偏函数应用示例,代码如下:
let ask student ``a question`` =
printf "me ask %s: %s" student ``a question``
let askJohn =ask "John"
askJohn "How old are you?"
注意以下几点:
- 代码没有显示指定入口点,程序会从第一行代码开始执行
- F#变量命名支持空格,只需将变量用``括起来即可
常量也是函数
open System
//常量函数
let f1(x)=2
let f2 x=2
//常量
let f3 =2
返回值(unit与ignore)
每个 F# 表达式的计算结果必须为一个值,对于不生成相关值的表达式,使用 unit 类型的值。
unit 类型类似于 C# 和 C++ 等语言中的 void 类型,参考 unit 类型。
unit 类型具有单个值,该值由标记 () 指示,经常在 F# 编程中用于保存值是语言语法所必需的位置(但实际不需要任何值)。
因为 printf 操作的重要操作发生在函数中,所以函数不必返回实际值。 因此,printf 函数的返回值为 unit 类型,代码如下:
//返回值为unit
let print=printfn "check here %d"
print 3
//print2为unit类型值,内部会直接执行打印
let print2= print 4
//print2
//print3为函数,传入参数内部才会执行打印
let print3 _= print 5
print3 ()
关于unit类型的使用可以参考 F# 之 Unit 與 Ignore 簡介。
如果函数返回的不是unit类型并且程序没有处理返回值,就需要使用ignore消除警告:
let sum x y = x + y
sum 2 3 |> ignore
函数串联实现“开方乘十”
“开方”和“乘十”是两个操作,可以通过 函数正向组合运算符 >> 组合两个函数,执行先开方再乘十的操作,代码如下:
//函数重命名
let 开方=sqrt
//操作符重定义
let 乘十=(*)10.
//函数串联
let 开方乘十=开方>>乘十
//36后面必须加一个 . 符号表示类型为float,否则不能进行sqrt运算
let result=开方乘十 36.
printfn $"result={result}"
操作符重定义代码需要注意,运算符是用括号括起来的特殊名称的函数-参考 F#运算符重载。代码 let 乘十=(*)10. 相当于将基于加运算操作生成一个偏函数,所以后面的“10”替换的是加操作的前一个操作数。
一个简单的示例,代码如下:
//操作符重定义
let ``inc 1``=(+)1
let toKiloMeter=(*)1.6
let ``20 div``=(/)20
let result1=``inc 1`` 50
printfn $"result={result1}"
//打印结果:result=51
let result2=``20 div`` 4
printfn $"result={result2}"
//打印结果:result=5
使用管道符 |>
管道符 |>是F#内部实现的一个符号,其作用是将左侧表达式的结果传递给右侧的函数,实现代码其实很简单:
let inline (|>) x f = f x
继续使用“开方乘十”的示例,代码如下:
let 开方=sqrt
let 乘十=(*)10.
let 开方乘十=开方>>乘十
let 学生分数=60.0
let result=开方乘十 学生分数
printfn $"result={result}"
使用管道符可以让代码读写变得更轻松:
//原代码
//let result=开方乘十 学生分数
//使用管道符的代码
let result=学生分数 |> 开方乘十
也可以把“开方乘十”函数展开:
let result=学生分数 |> 开方 |> 乘十
格式化一下代码会更容易阅读:
let result=
学生分数
|>开方
|>乘十
元组(参数加上括号)
元组是一组未命名但有序的值,值的类型可能不同。 元组可以是引用类型或结构,具体定义参考 元组。
元组语法如下:
//引用元组
(element, ... , element)
//结构元组
struct(element, ... ,element )
注:上述语法中的每个 element 都可以是任何有效的 F# 表达式。
一个应用元组计算两点距离的示例:
let distance(x0,y0)(x1,y1)=sqrt((x0-x1)**2. + (y0-y1)**2.)
//不加括号定义元组
let a=2. ,2.0
//加括号定义元组
let original=(0.,0.)
let x=a |>distance original
printfn "%A" x
一个元组解构取值的示例:
let tuple=(1,2,3)
let b=(2,3,5)
let c=(tuple,12,13)
let (x,_,z)= tuple
let a,_,_=c
let(x0,y0,z0),_,_=c
printfn $"{x} and {z}"
printfn $"{a} and {x0}"
注:用_表示不关注的值,否则需要为所有不关注的值取一个不重复的名称。
元组只适合内部运算使用,不要将元组用于外部交互,这里的“外部”包括用户、同事甚至一段时间后的自己。
F#中的类
F#作为函数式编程语言,提供类的定义主要基于以下两方面考虑:
- 与其它面向对象语言交互
- 作为一门成熟的语言并不排斥面向对象中一些成熟的概念,比如类
在F#中类只是组织一些数据、一些方法的组织结构,定义类只需要使用type关键字即可。
一个定义狗的类,代码如下:
type Dog()=
member val Age=2 with get,set
member val Breed="狼狗" with get,set
member this.叫()="汪汪"
member this.哭()=printfn "呜呜"
let dog=Dog()
dog.叫()|>printfn "%s"
dog.哭()
F#中的类也支持构造函数、继承、虚方法等面向对象中的概念,有兴趣的可以看看 类 (F#)。
记录
记录表示已命名值的简单聚合,可选择包含成员。 记录可以是结构或引用类型, 默认情况下是引用类型,示例如下:
//在同一行上定义标签时,标签用分号分隔
type Point = { X: float; Y: float; Z: float; }
//您可以在自己的行上定义标签,可以使用分号,也可以不使用分号
type Customer =
{ First: string
Last: string;
SSN: uint32
AccountNumber: uint32; }
//结构记录
[<Struct>]
type StructPoint =
{ X: float
Y: float
Z: float }
注:记录就是带了名字的元组,值会按照记录的域名自动匹配记录类型。如果遇到域名完全一样的记录,值的类型会匹配到最后定义的那个记录类型。
复制和更新记录表达式
复制和更新记录表达式是一个复制现有记录、更新指定字段并返回更新后的记录的表达式。
let myRecord2 = { MyRecord.X = 1; MyRecord.Y = 2; MyRecord.Z = 3 }
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
元组、记录、类的对比
一个简单的对比示例:
let dog0=("Tao","German shepherd","Lucky",3)
//匿名记录
let dog={|Owner="Tao";Breed="German shepherd";DogName="Lucky";Age=3;|}
type Dog()=
member val Owner="Tao" with get
member val Breed="German shepherd" with get
member val DogNmae="Lucky" with get
member val Age=3
let dog2=Dog()
三者优缺点如下:
- 元组:语法简单、方便,适用于函数内部或一些示例,不能用于外部交互
- 记录:语法适中、结构清晰,大多数情况都可以使用记录
- 类:语法略复杂,使用代价比记录略大,一般用的较少
总的来说,什么时候使用元组、记录、类要自己根据具体的场景来判断。
联合
F#中的联合类似于其他语言中的联合类型,但存在一些差异,具体区别参考 可区分联合。
一个简单的联合示例:
//Union,DU,or 联合曰type Point=
type Point=
|TwoD of int*int
|ThreeD of int*int*int
|OneD of int
let p1=OneD(3);
let p2=TwoD(1,2)
let p3=ThreeD(2,3,5)
let printPoint (p:Point)=
printfn "%A" p
printPoint p2
printPoint p3
//打印结果
//TwoD (1, 2)
//ThreeD (2, 3, 5)
使用联合做统一操作
在面向对象中我们一般通过基类对不同类型的对象做统一操作,这种方法需要更改代码使操作对象继承同一个基类。
F#可以使用联合来实现对不同类型的对象做统一操作,并且不需要定义基类,代码如下:
//Uni//Union,DU,or 联合
type Dog={Owner:string;Breed:string;DogName:string;Age:int}
let dog ={Owner="Tao";Breed="corgi";DogName="Lucky";Age=2;}
type Cat()=
member val Owner="Tao"with get,set
member val Name="Lovely"with get,set
member val Age=2 with get,set
let cat=new Cat()
//定义联合
type Animal=
|狗 of Dog
|猫 of Cat
let printName animal=
//模式匹配
match animal with
|狗 d->printfn "%A" d.DogName
|猫 c->printfn "%A" c.Name
let d=狗 dog
let c=猫 cat
printName d
printName c
//打印结果
//"Lucky"
//"Lovely"
注:match 表达式提供基于表达式与一组模式的比较结果的分支控制,详细说明参考 模式匹配。
使用联合进行分类
联合不仅可以进行基类操作,还可以进行分类,代码如下:
type Category=
| Zero
| Small of int
| Big
| Huge
| VeryHuge
let categorize x=
match x with
| 0 -> Zero
| 1
| 2 -> Small(x)
| _ when x>2 && x<10 -> Big
| _ when x>=10 && x<100 -> Huge
| _ ->VeryHuge
categorize 2
//交互执行结果:val it : Category = Small 2
实现树数据结构
联合可以是递归的,可将联合本身包含在一个或多个用例的类型中用于创建树结构,这些结构用于在编程语言中对表达式建模。
一个二叉树数据结构示例:
type Tree =
| Tip
| Node of int * Tree * Tree
let rec sumTree tree =
match tree with
| Tip -> 0
| Node(value, left, right) ->
value + sumTree(left) + sumTree(right)
let myTree = Node(0, Node(1, Node(2, Tip, Tip), Node(3, Tip, Tip)), Node(4, Tip, Tip))
let resultSumTree = sumTree myTree
// resultSumTree值为10
联合包括两个用例,即 Node(具有整数值以及左子树和右子树的节点)和 Tip(用于终止树),树的结构如下:
IF和多分支
F#中的if和其它语言类似,只是必须带else,否则报错。F#中的分支控制是由match负责,没有switch关键字。
示例如下:
//if必须带else
let f a=if a%2=0 then "even"else "odd"
//模式匹配(类似switch分支)
let f2 a=
match a with
| 0 -> "Zero"
| 1 -> "One"
| 2 -> "Two"
//当a大于2且小于100时
| _ when a > 2 && a < 180 -> "big number"
//当a大于等于100时
| _ when a >= 100 -> "huge number"
//抛出异常 "I do not understand this number"
| _ -> failwith "I do not understand this number"
注:failwith 函数会生成 F# 异常,用法参考 异常:failwith 函数。
不可变的变量
F# 认为不可变值最为重要,而不是可在程序执行过程中赋予新值的变量, 不可变数据是函数编程中的一个重要元素,参考 值。
如果过程语言中的代码使用变量赋值来更改值,函数语言中的等效代码会有一个作为输入的不可变值、一个不可变函数以及作为输出的其他不可变值。
//immutable 不可变变量
let a=2
//mutable 可变变量
let mutable b=3
//将新值赋给可变变量
b<-5
//交换值
let swap(a,b)=b,a
let x=swap(2,4)
不使用NULL(Some和None)
F#中有NULL值,主要用来接受其它语言的NULL值,内部是不会使用NULL值的,参考 Null 值。
F#使用Some将数据分为:
- 可用(Some):符合函数预期的合法值
- 不可用(None):不符合函数预期的非法值(包括Null)
Some、None属于 Option 联合类型的用例,Option 类型是 F# 核心库中的简单可区分联合,声明方式如下:
type Option<'a> =
| Some of 'a
| None
一个判定数据合法、非法的示例:
let a=2
//只有0是合法值
let makeOption x=
if x=0 then None
else Some(x)
let f b=
match b with
| Some(_)->"average value"
| None->"odd point"
a |> makeOption |> f
学习笔记-涛讲F#(基础)的更多相关文章
- 【学习笔记】JavaScript的基础学习
[学习笔记]JavaScript的基础学习 一 变量 1 变量命名规则 Camel 标记法 首字母是小写的,接下来的字母都以大写字符开头.例如: var myTestValue = 0, mySeco ...
- Linux 学习笔记之超详细基础linux命令(the end)
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 14---------------- ...
- Linux 学习笔记之超详细基础linux命令 Part 13
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 12---------------- ...
- Linux 学习笔记之超详细基础linux命令 Part 12
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 11---------------- ...
- Linux 学习笔记之超详细基础linux命令 Part 11
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 10---------------- ...
- Linux 学习笔记之超详细基础linux命令 Part 10
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 9----------------- ...
- Linux 学习笔记之超详细基础linux命令 Part 9
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 8----------------- ...
- Linux 学习笔记之超详细基础linux命令 Part 7
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 6----------------- ...
- Linux 学习笔记之超详细基础linux命令 Part 6
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 5----------------- ...
- Linux 学习笔记之超详细基础linux命令 Part 5
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 4----------------- ...
随机推荐
- 3.1 C++ STL 双向队列容器
双向队列容器(Deque)是C++ STL中的一种数据结构,是一种双端队列,允许在容器的两端进行快速插入和删除操作,可以看作是一种动态数组的扩展,支持随机访问,同时提供了高效的在队列头尾插入和删除元素 ...
- C/C++ Qt 信号自定义槽函数
Qt中实现自定义信号与槽函数,信号用于发送并触发槽函数,槽函数则是具体的功能实现,如下我们以老师学生为例子简单学习一下信号与槽函数的使用方法. 使用无参数信号与槽: 首先定义一个teacher类,该类 ...
- 使用DoraCloud免费版搭建办公桌面云
DoraCloud是一款多平台的桌面虚拟化管理软件,支持Hyper-V.VMware.Proxmox.XenServer等多种虚拟化平台.DoraCloud在虚拟化平台上具有极大的灵活性,允许您的组织 ...
- Hadoop-Operation category READ is not supported in state standby 故障解决
在查询hdfs时或者执行程序向hdfs写入数据时遇到报错:Operation category READ is not supported in state standby 意思是:该主机状态为待机, ...
- 从零开始的react入门教程(四),了解常用的条件渲染、列表渲染与独一无二的key
壹 ❀ 引 在从零开始的react入门教程(三),了解react事件与使用注意项一文中,我们了解了react中事件命名规则,绑定事件时对于this的处理,以及事件中可使用的e对象.那么这篇文章中我们来 ...
- NC24911 数独挑战
题目链接 题目 题目描述 数独是一种填数字游戏,英文名叫 Sudoku,起源于瑞士,上世纪 70 年代由美国一家数学逻辑游戏杂志首先发表,名为 Number Place,后在日本流行,1984 年将 ...
- CentOS 7.3 源码安装squid 4.12 及安装过程遇到的一些问题
一.源码安装squid 4.12 1.下载squid-4.12源码包 wget http://www.squid-cache.org/Versions/v4/squid-4.12.tar.gz tar ...
- Ubuntu在无网络环境下,用离线源apt-get安装软件
步骤概要如下: 1.假设目标安装的是服务器A,需先准备一台正常环境,且操作系统版本与A一致的服务器B: 2.用apt-get在服务器B上下载需要安装的包,并用dpkg-scanpackages依赖打包 ...
- CSS加JS实现网页返回顶部功能
最近在设计自己的博客,前端页面在内容很多的时候往下拖动会有滚动条.通常我们都需要一个返回顶部的功能来实现快速来到网页顶部.当然实现方式不止一种,这里我采用的最实用的一种.使用CSS+Jquery方式 ...
- Innodb之事务
目录 一.事务基本概念 事务的特性:ACID 事务类型 1.扁平事务 2.带保存点的扁平事务 3.链式事务 4.嵌套事务 5.分布式事务 二.事务的实现概述 三.redo log 1)组成 2)red ...