处理一堆数

一个处理集合数据的示例:

let a=[1..10]    //List
let b=[| 1..10|] //Array //for循环打印
for i in a do
printfn"%d,"i //打印每个数
a |>Seq.iter(printfn"%d") //将数转成函数
let makeF x=fun()->printfn"%d"x
a |>Seq.map makeF |>Seq.iter(fun f->f()) //将数转成函数再相加
let y=a |> Seq.map makeF |> Seq.reduce(>>)
y()

上面的示例需要注意几个知识点:

以数组为例,创建数组的示例如下:

//用分号分隔来创建一个小型数组
let array1 = [| 1; 2; 3 |] //每个元素各占一行,分号分隔符可选
let array1 =
[|
1
2
3
|] //使用序列表达式来创建数组
let array3 = [| for i in 1 .. 10 -> i * i |] //创建一个所有元素都初始化为零的数组
let arrayOfTenZeroes : int array = Array.zeroCreate 10 //在每个索引上调用给定的生成器来创建一个列表
let list = List.init 4 (fun x->0)

组织代码(命名空间、模块)

命名空间的定义参考 命名空间 (F#),注意以下几点:

  • 如果要将代码放在命名空间中,文件中的第一个声明必须声明该命名空间, 整个文件的内容将成为命名空间的一部分。
  • 如果文件中存在其他命名空间声明,则直到下一个命名空间声明之前的所有代码都被认为在第一个命名空间内。
  • 命名空间不能直接包含值和函数,值和函数必须包含在模块中, 命名空间可以包含类型和模块

模块的定义参考 模块,注意以下几点:

  • F# 模块是一组 F# 代码构造,例如类型、值、函数值和 do 绑定中的代码
  • 模块声明有两种类型,具体取决于整个文件是否包含在模块中:顶级模块声明局部模块声明顶级模块声明在模块中包括整个文件

实验记录:将一个Namespace下的若干Module编译为dll后使用C#调用,可以看到在C#中,module被视作class,而module中的函数被视作class的公共静态Method,变量被视作class的静态只读Property

很多人确实这么做了,可以帮助理解,对于混合编程也有好处。等你习惯了以后,尽量使用脱离C#的想法来思考F#。就象学外语一样,老翻译成中文学,刚开始可以,习惯以后还是要用外语思考的好。通常来说,F#编译器产生的代码和C#产生的是不一样的,当IL被转成一个C#以后,会造成错觉的

新建一个 Module 文件,新建的文件会在已有文件的前面且不能调换顺序:



Module.fs文件内容:

namespace MyNamespace

//命名空间不能定义函数
//let f3()=()
type stringAlias=String module Module1=
let printInt =printfn "%d" module Module2=
let f()=printfn "in function f"

Program.fs文件内容:

open MyNamespace
open Module1 let a=[1..10]//List
let b=[| 1..10|]//Array for i in a do
printfn "%d,"i
MyNamespace. Module2.f() a |>Seq.iter printInt

使用联合重命名类型

使用联合重命名类型有点类似于C++的typedef关键字,但F#中多了类型推断程序更不容易出现bug。F#编译器做了很多优化,不能担心联合重命名类型时的性能。

type HumanName = |HumanName of string
type DogName = | DogName of string //推断输入参数类型
let f=function
|HumanName(n)->printfn "%s"n let dogName=DogName("lucky");
let name=HumanName("Tao")
f name

类必须显式转换成接口

接口方法只能通过接口调用,不能通过实现接口的类型的任何对象调用。 需要使用 :> 运算符或 upcast 运算符向上转换到接口类型,才能调用这些方法,如以下代码所示:

//接口定义
type IAsType<'T>=
abstract member NewValue:'T with get, set type MyClass()=
let mutable v=8 interface IAsType<int>with
member this.NewValue
with get()=v
and set(i)=v<-i interface IAsType<string>with
member this.NewValue
with get()=v.ToString()
and set(i)=v <- System. Convert.ToInt32(i) let mc=MyClass()
//显示转换
let a=mc:>IAsType<int>
let b=mc:>IAsType<string>
a.NewValue <- 4
b.NewValue

注:在泛型语法中的type-parameters 是一个表示未知类型的参数的逗号分隔列表,其中每个参数都以单引号开头,并且可选择包含一个约束子句,用来进一步限制可用于该类型参数的类型。

对象表达式

对象表达式是介于module和class之间的代码组织方式,可用于创建动态创建的匿名对象类型的新实例,该对象类型基于现有基类型、接口或接口集。

//object expression
type IA=
abstract MyFunction: int->int
abstract MyValue: string with get, set let myFunction i=i+1
let mutable str ="Hello" let a=
{
new IA with
member this. MyFunction i=myFunction i
member this. MyValue
with get()=str
and set(i)=str<-i
}

递归函数

递归函数:rec 关键字与 let 关键字一起用于定义递归函数,参考 递归函数:rec 关键字

一个简单的递归函数示例:

let l=[1L..100L]
let rec sumBad l=
match l with
|[]->0L
|h::t->h+(sumBad t) let ybad=sumBad l
printfn "%A"ybad

CPS解决堆栈溢出

上面的递归示例的原始数据如果变成 let l=[1L..1000000L] 则会产生堆栈溢出,使用CPS可以避免堆栈溢出。

CPS是函数编程语言里面常识,有两种翻译:

  • continuous passing style(CPS)
  • continuous programming style(CPS)

使用CPS必须在“解决方案”->“属性”->"生成"里面勾选“生成尾调用”,改造后递归函数示例:

let l=[1L..1000000L]
//cont是continue的缩写,表示递归的结束函数
let rec sum l cont=
match l with
|[]->cont 0L
|h::t->
sum t (fun x->cont(h+x))
//id函数是Operators模块里面的函数,会返回输入的参数值
let y=sum l id
printfn "%A"y

有两个递归调用的CPS:

//List.filter类似于LINQ的where关键字
let rec qs list cont =
match list with
|[]->cont([])
|[a]->cont([a])
| head:: tail->
let lessList=tail |>List.filter(fun i->i<=head)
let moreList=tail |>List.filter(fun i->i>head)
qs lessList (fun lessListPara -> //lesslistPara=gs lesslist
qs moreList (fun moreListPara->cont(lessListPara@[head]@ moreListPara))) let list=[0;7;2;6;8;4;1;12]
let result=qs list id
for i in result do
printfn"%A"i

上面的函数作用是排序列表,先取头元素然后筛选出比头元素小的左列表、比头元素大的右列表,分别递归排序完左列表、右列表,最后按 左列表+头元素+右列表 组合出结果列表。

扩展一个类型

类似于C#中的扩展方法,参考 类型扩展,示例代码如下:

type Variant=
| HugeNumber of int
| BigNumber
| SmallNumber module FunctionLibrary=
// function 用作 fun 关键字和 Lambda 表达式中对单个参数进行模式匹配的 match 表达式的较短替代项
// let print = function 等价于 let print x = match x with
let print=function
| HugeNumber n->printfn "Num %d"n
| BigNumber->printfn $"{nameof(BigNumber)}"
| SmallNumber->printfn "Small number" //为Variant 添加扩展
type Variant with
//x只是自标识符,可以任意取名,如this、self
member x.Print()=FunctionLibrary. print x let a=Variant.SmallNumber
let b=Variant.HugeNumber 100
//个人感觉使用管道符调用更好看一点,符合函数思想
a |>FunctionLibrary. print
b.Print()

静态解析的类型参数

静态解析的类型参数是一种类型参数,它在编译时(而不是在运行时)被替换为实际类型。 它们前面有一个插入符号 (^),参考 静态解析的类型参数

在 F# 中,有两种不同的类型参数:

  • 标准泛型类型参数:参数由撇号 (') 表示,如 'T 和 'U,等同于其他 .NET Framework 语言中的泛型类型参数。
  • 静态解析的参数:用一个插入符号表示,如 ^T 和 ^U

静态解析的类型参数示例如下:

type Adder()=
member this. Add(a,b)=a-b let inline add (obj:^T) a b=(^T:(member Add: int->int->int)(obj,a,b))
let adder=Adder()
printfn"%d"(add adder 2 3)

上面的add函数类似一种反射调用,前面的类型约束相当于是对实例方法的filter(类似GetMethod),后面的括号就是对反射获取的方法进行调用(类似Invoke),第一个参数是实例,后面则是参数列表。

注:定义类时,成员函数的参数括号不是代表元组,只是为了与C#保持语法上面的一致,简单来说示例中的Add(a,b)等价于Add a b

ref变量的实现原理及应用

ref变量本质上是通过一个非常简单的记录来实现的,该记录包含一个可变记录字段,一个简单的示例:

//定义
let a=ref 1
//赋值
a:=3
//通过Value赋值
a.Value<-5 //读取
printfn "%d"!a
//通过Value读取
printfn "%d"a.Value //引用同一个记录
let b = a
b := 10
printfn "%d"!a

参考 F#: let mutable vs. ref,ref的实现原理如下:

type ref<'T> =  // '
{ mutable value : 'T } // ' // the ref function, ! and := operators look like this:
let (!) (a:ref<_>) = a.value
let (:=) (a:ref<_>) v = a.value <- v
let ref v = { value = v }

F#资源网站

F#学习、工作中的一些资源网站:

Result数据类型

Result<'T,'TFailure>类型允许您编写可组合的容错代码,参考 Result模块

结果类型是struct 可区分的 union,结构相等语义适用于此。该类型通常用于一元错误处理,在 F# 社区Result中通常称为面向铁路的编程

下面的示例演示了Result的用法和一些简单数据类型转换:

//一元二次方程有没有实根
let f (a,b,c)=
let delta=b**2.-4.*a*c
if(delta<0.0) then Error "没实根"
else Ok delta //数据类型的转换方法
let x=f(1.,float(2),4 |>float)

注:其它类型转换方法参考 强制转换和转换 (F#)

面向铁路的编程与if...else类似,函数内部对Result做模式匹配:

let process0=function
|Ok x->
if x > 0 then
//单行注释
(* 多行注释*)
printfn "valid value, continue"
Ok(System.Random().Next(0,3))
else Error "Must be positive"
//使用as对Error变量重命名
| Error _ as y->y //铁道模式
(Ok 1)|>process0 |>process0 |>process0 |>ignore

异常处理

F# 中有两种异常类别:.NET 异常类型F# 异常类型,通常不提倡在F#中使用异常,参考 异常类型

常量 Literal

F#中有const关键字,但是作为关键字保留,以供将来扩充 F#。

可以使用 Literal 属性标记旨在成为常量的值, 此属性具有导致将值编译为常量的效果:

[<Literal>]
let π=3.1415926

AutoOpen 属性

如果要在引用某个程序集时自动打开命名空间或模块,可以将 AutoOpen 属性应用于该程序集。 还可以将 AutoOpen 属性应用于某模块,以在打开父模块或命名空间时自动打开该模块,参考 AutoOpenAttribute

[<AutoOpen>]
module MyModule=
let a=100 //不使用AutoOpen只能提供MyModule.a访问
printfn "%d" a

学习笔记-涛讲F#(基础 II)的更多相关文章

  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. Dart常用核心知识

    Dart简述 Dart 是一个为全平台构建快速应用的客户端优化的编程语言,免费且开源. Dart是面向对象的.类定义的.单继承的语言.它的语法涵盖了多种语言的语法特性,如C,JavaScirpt, J ...

  2. 从零开始教你手动搭建幻兽帕鲁私服( CentOS 版)

    哈喽大家好,我是咸鱼. 想必上网冲浪的小伙伴最近都被<幻兽帕鲁>这款游戏刷屏了. (文中图片均来自网络,侵删) 幻兽帕鲁是 Pocketpair 打造的一款开放世界的生存建造游戏.在游戏中 ...

  3. shell find 根据时间获取文件列表

    根据时间得到文件,可以使用find进行查找,支持查找: find以时间为条件查找可用选项: -amin n:查找n分钟以前被访问过的所有文件. -atime n:查找n天以前被访问过的所有文件. -c ...

  4. 超简单实用的4个PPT操作技巧

    作为我们IT岗位的兄弟姐妹们,一定少不了各种PPT的展示,很多IT大佬总是不屑于PPT的美观,认为只要演讲有干货,格式无所谓,甚至都不需要PPT. 话是这样说,但其实无非就是觉得调整美化实在是浪费时间 ...

  5. Linux如何禁用透明大页

    环境: RHEL 6.5 + Oracle 11.2.0.4 RAC 1.确认透明大页是否开启 grep HugePage /proc/meminfo cat /sys/kernel/mm/redha ...

  6. GCD,乘法逆元

    最大公约数 公约数:几个整数共有的约数.($ \pm 1是任何整数的公约数$) 最大公约数:显而易见,所有公约数中最大的那个. 欧几里得算法 为了求最大公约数(常记为GCD),我们常用欧几里得算法.以 ...

  7. HBase-compact介绍(minor和major区别)

    一.minor和major的区别: Minor Compaction:指选取一些小的.相邻的HFile将他们合并成一个更大的HFile,但不会清理过期(TTL)和删除(打上Delete标记)的数据.  ...

  8. Linux-sshpass(shell脚本使用ssh远程执行命令通过密码的方式登录)

    1. sshpass简介 sshpass 是一个在非交互式 ssh 会话中自动输入密码的工具.它可以直接在命令行中指定密码,因此可以用于 Shell 脚本等自动化场景.在 Red Hat 系统中,可以 ...

  9. NC16641 [NOIP2007]守望者的逃离

    题目链接 题目 题目描述 恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变.守望者在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上.为了杀死守望者,尤迪安开始对这个荒岛施咒 ...

  10. Js模块化导入导出

    Js模块化导入导出 CommonJs.AMD.CMD.ES6都是用于模块化定义中使用的规范,其为了规范化模块的引入与处理模块之间的依赖关系以及解决命名冲突问题,并使用模块化方案来使复杂系统分解为代码结 ...