简介

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)

SomeNone属于 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#(基础)的更多相关文章

  1. 【学习笔记】JavaScript的基础学习

    [学习笔记]JavaScript的基础学习 一 变量 1 变量命名规则 Camel 标记法 首字母是小写的,接下来的字母都以大写字符开头.例如: var myTestValue = 0, mySeco ...

  2. Linux 学习笔记之超详细基础linux命令(the end)

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 14---------------- ...

  3. Linux 学习笔记之超详细基础linux命令 Part 13

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 12---------------- ...

  4. Linux 学习笔记之超详细基础linux命令 Part 12

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 11---------------- ...

  5. Linux 学习笔记之超详细基础linux命令 Part 11

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 10---------------- ...

  6. Linux 学习笔记之超详细基础linux命令 Part 10

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 9----------------- ...

  7. Linux 学习笔记之超详细基础linux命令 Part 9

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 8----------------- ...

  8. Linux 学习笔记之超详细基础linux命令 Part 7

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 6----------------- ...

  9. Linux 学习笔记之超详细基础linux命令 Part 6

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 5----------------- ...

  10. Linux 学习笔记之超详细基础linux命令 Part 5

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 4----------------- ...

随机推荐

  1. 3.1 C++ STL 双向队列容器

    双向队列容器(Deque)是C++ STL中的一种数据结构,是一种双端队列,允许在容器的两端进行快速插入和删除操作,可以看作是一种动态数组的扩展,支持随机访问,同时提供了高效的在队列头尾插入和删除元素 ...

  2. C/C++ Qt 信号自定义槽函数

    Qt中实现自定义信号与槽函数,信号用于发送并触发槽函数,槽函数则是具体的功能实现,如下我们以老师学生为例子简单学习一下信号与槽函数的使用方法. 使用无参数信号与槽: 首先定义一个teacher类,该类 ...

  3. 使用DoraCloud免费版搭建办公桌面云

    DoraCloud是一款多平台的桌面虚拟化管理软件,支持Hyper-V.VMware.Proxmox.XenServer等多种虚拟化平台.DoraCloud在虚拟化平台上具有极大的灵活性,允许您的组织 ...

  4. Hadoop-Operation category READ is not supported in state standby 故障解决

    在查询hdfs时或者执行程序向hdfs写入数据时遇到报错:Operation category READ is not supported in state standby 意思是:该主机状态为待机, ...

  5. 从零开始的react入门教程(四),了解常用的条件渲染、列表渲染与独一无二的key

    壹 ❀ 引 在从零开始的react入门教程(三),了解react事件与使用注意项一文中,我们了解了react中事件命名规则,绑定事件时对于this的处理,以及事件中可使用的e对象.那么这篇文章中我们来 ...

  6. NC24911 数独挑战

    题目链接 题目 题目描述 数独是一种填数字游戏,英文名叫 Sudoku,起源于瑞士,上世纪 70 年代由美国一家数学逻辑游戏杂志首先发表,名为 Number Place,后在日本流行,1984 年将 ...

  7. 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 ...

  8. Ubuntu在无网络环境下,用离线源apt-get安装软件

    步骤概要如下: 1.假设目标安装的是服务器A,需先准备一台正常环境,且操作系统版本与A一致的服务器B: 2.用apt-get在服务器B上下载需要安装的包,并用dpkg-scanpackages依赖打包 ...

  9. CSS加JS实现网页返回顶部功能

    最近在设计自己的博客,前端页面在内容很多的时候往下拖动会有滚动条.通常我们都需要一个返回顶部的功能来实现快速来到网页顶部.当然实现方式不止一种,这里我采用的最实用的一种.使用CSS+Jquery方式 ...

  10. Innodb之事务

    目录 一.事务基本概念 事务的特性:ACID 事务类型 1.扁平事务 2.带保存点的扁平事务 3.链式事务 4.嵌套事务 5.分布式事务 二.事务的实现概述 三.redo log 1)组成 2)red ...