处理一堆数

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

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. 2.2 Windows驱动开发:内核自旋锁结构

    提到自旋锁那就必须要说链表,在上一篇<内核中的链表与结构体>文章中简单实用链表结构来存储进程信息列表,相信读者应该已经理解了内核链表的基本使用,本篇文章将讲解自旋锁的简单应用,自旋锁是为了 ...

  2. 5.13 汇编语言:仿写For循环语句

    循环语句(for)是计算机编程中的一种基本控制结构,它允许程序按照指定的次数或范围重复执行一段代码块.for循环在处理需要进行迭代操作的情况下非常有用,它使得程序可以更加方便地控制循环的次数.一般来说 ...

  3. 有用的sql笔记(工作总结)

    1.查询当前月(数字为0表示当前月份,1表示上个月,-1表示下个月,以此类推) SELECT DATE_FORMAT((CURDATE() - INTERVAL [数字] MONTH), '%Y-%m ...

  4. 音乐播放器 — 用 vant4 中的滑块自定义播放器进度条

    一.运行效果 二.代码实现 2.1.HTML: <!-- 音频播放器 --> <audio ref="audio" src="音乐名称.mp3" ...

  5. HBase-Hbase启动异常java.lang.IllegalArgumentException: object is not an instance of declaring class

    1.问题描述 HBase启动时异常如下: java.lang.IllegalArgumentException: object is not an instance of declaring clas ...

  6. 剑指Offer07 重建二叉树

    剑指 Offer 07. 重建二叉树 前置概念: 前序:访问根节点,先序遍历左子树,先序遍历右子树: 中序:中序遍历左子树,访问根节点,中序遍历右子树: 后序:后序遍历左子树,后序遍历右子树,访问根节 ...

  7. Pandas分组聚合

    groupby分组操作详解 在数据分析中,经常会遇到这样的情况:根据某一列(或多列)标签把数据划分为不同的组别,然后再对其进行数据分析.比如,某网站对注册用户的性别或者年龄等进行分组,从而研究出网站用 ...

  8. JS leetcode 至少是其他数字的两倍的最大数 解答思路分析

    壹 ❀ 引 刷leetcode的第二天,那么今天做的也是一道难度为简单的题目至少是其他数字的两倍的最大数,老规矩,先说说我的实现思路后,再来分析优质答案,原题如下: 在一个给定的数组nums中,总是存 ...

  9. NC25879 外挂

    题目链接 题目 题目描述 我的就是我的,你也是我的,记住了,狐狸! ​ --韩信-白龙吟 对于打赌输了的小T会遭受到制裁,小s修改了数据库使他可以派出许多军队来围攻小T. 很不幸,小T与小s打赌打输了 ...

  10. Widget模式

    Widget模式 Widget模式是指借用Web Widget思想将页面分解成组件,针对部件开发,最终组合成完整的页面,Web Widget指的是一块可以在任意页面中执行的代码块,Widget模式不属 ...