当前位置: 首页 > 编程日记 > 正文

F#探险之旅(三):命令式编程(上)

F#系列随笔索引页面

在本系列的第二部分(函数式编程上、中、下)中,我们了解了如何使用F#进行纯粹的函数式编程。但是在一些情况下,比如I/O,几乎不能避免改变状态,也就是说会带来side effect。F#并不强求你以无状态的方式编写程序,它提供了可修改(mutable)的标识符来解决这类问题,同时它还提供了其它的程序结构以支持命令式编程。现在就来对这些特性探个究竟。

首先是unit类型,这种类型表示“没有值”。然后是F#如何处理可修改的值。最后来看看如何在F#中使用.NET类库,包括如何调用静态方法、创建对象并使用其成员、使用类的索引器和事件以及F#中的|>操作符。

unit类型 

没有参数和返回值的函数的类型为unit,它类似于C#中的void,或者说CLR中的System.Void。对于使用函数式编程语言的开发人员来说,不接受参数、无返回值的函数没多大意义。而在命令式编程中,由于side effect的存在,这样的函数仍有意义。unit类型表示为一对括号“()”。

F# Code
let main() =
()


在这个例子中,main函数的类型为unit -> unit,也就是说它既不接受参数,也无返回值。第一对括号使得main成为一个函数而不是一个简单的值。第二对括号告诉编译器,main函数什么值也不返回。

注意:仅仅将函数命名为main不表示它就是程序的入口,它不会自动执行(像C#的Main方法那样),要执行它,需要在源文件的结尾处调用:main(),在后面的文章中将介绍如何指定F#程序的入口。

再来看看如何调用unit类型的函数,有两种方式:

F# Code
let () = main()
// -- or --
main()


我们还可以在函数内部连续多次调用unit类型的函数——只要保证它们的缩进是一样的即可:

复制代码
F# Code
let poem() =
print_endline
"I sent thee late a rosy wreath"
print_endline
"Not so much honouring thee"
print_endline
"As giving it a hope that there"
print_endline
"It could not withered be"

poem()
复制代码


这是《Song to Celia》中的四句诗词,print_endline函数的类型为string -> unit,我们知道函数的返回值为其函数体内最后一步运算的值,所以poem的类型为unit -> unit。

并不是说只有unit类型的函数可以这么用,但是调用非unit类型的函数时编译器会报告一个警告,因为它可能会有副作用(side effect),比如:warning FS0020: This expression should have type 'unit', but has type 'string',我们看到警告总会感觉不舒服。F#提供了一些机制可将这些警告消除,即将函数转换为unit类型的函数。事实上,这种需求在使用F#库时不多,在使用由其它语言编写的.NET类库时会更多些。看下面的例子:

复制代码
F# Code
#light
let getString() = "foo bar"

let _ = getString()
// -- or --
ignore(getString())
// -- or --
getString() |> ignore
复制代码


首先是函数getString的定义,下面的三行则是将其转换为unit类型函数的三种方式。第一种是使用下划线“_”,它在前面已经出现过几次,它一般表示我们对某些值不感兴趣;既然不感兴趣,那就可以忽略(ignore)了,这就是第二种方式:ignore函数;第三种方式使用了“|>”操作符,它本质上同第二种一样。“|>”操作符会在稍后介绍。

mutable关键字 

在探险之旅(二)中我们知道可以使用let关键字将值绑定至标识符,在某些情况下,我们还可以重定义(redefine)标识符或者绑定(rebind)新的值,但是不能直接修改它的值。显然,对于我们这些用惯了命令式编程语言的人来说,这实在有些不爽,因为这些语言以修改变量的值作为最基本的运算方式。既然F#也支持命令式编程范式,它当然也能让你修改标识符的值。这就是mutable关键字和“<-”操作符,“<-”的类型为unit(操作符也是函数),下面的例子对此作了演示:

F# Code
let mutable phrase = "Good good study, "
print_endline phrase
phrase
<- "day day up."
print_endline phrase


运行结果为:

Output
Good good study,
day day up.


这看起来像是重定义标识符,其实不然。修改标识符时,只能修改它的值而不能修改类型;重定义时可同时改变类型和值(这本质上是定义了一个新的标识符)。事实上,如果你尝试修改标识符的类型,编译器会给你一个错误。另外,它们还有一个重要的差别,即它们的可见性(或者说修改行为的作用域)。在重定义标识符的时候,修改仅仅在新标识符的作用域内有效,一旦出了这个作用域,它就会回复到旧有的值;对于可修改的标识符来说,任何修改都是永久性,与作用域无关。

复制代码
F# Code
let redefineX() =
let x = "One"
printfn
"Redefining: \r\nx = %s" x
if true then
let x = "Two"
printfn
"x = %s" x
else ()
printfn
"x = %s" x

let mutableX() =
let mutable x = "One"
printfn
"Mutating: \r\nx = %s" x
if true then
x
<- "Two"
printfn
"x = %s" x
else ()
printfn
"x = %s" x

redefineX()
mutableX()
复制代码


运行结果为:

复制代码
Output
Redefining:
X = One
X = Two
X = One
Mutating:
X = One
X = Two
X = Two
复制代码


可修改的标识符也有其局限性,在子函数内不能修改它的值。而这也是ref类型的来由,稍后你会看到。

定义可修改的记录(Record)类型 

默认情况下,记录类型是不可变的。不过F#提供了一种特殊的语法,使得我们可以修改记录类型的字段值,即在字段前使用mutable关键字。需要注意的是,这种操作改变的是记录字段的内容而不是记录本身

复制代码
F# Code
type Couple =
{her :
string; mutable him : string}
let couple = {her = "Elizabeth Taylor"; him = "Nicky Hilton"}

let print o = printf "%A \r\n" o

let changeCouple() =
print couple;
couple.him
<- "Michael Wilding";
print couple;
couple.him
<- "Michael Todd";
print couple;

changeCouple()
复制代码


通过Couple类型的定义可知,him字段是可修改的,就像changeCouple中的代码,但如果尝试修改her的值就会遭遇编译错误。

ref类型 

ref类型是一种状态进行修改的简单方式。ref类型其实是包含一个可修改字段的record类型,它定义在F#库中,伴随它的还有两个操作符,它们使得操作ref类型更为方便:

F# Code
let ref x = { contents = x }
let (:=) x y = x.contents <- y
let (!) x = x.contents


ref“函数”将输入的值“装入”一个记录类型,同时用“:=”操作符来进行赋值,“!”来取值。进一步分析,ref“函数”的类型为a’ -> Ref<a’>,可以了解到“装入”的记录类型为Ref<a’>,由此可知使用了类型参数化(type parameterization),这个概念前面部分已经介绍过了。这意味着ref可以接受任意类型的值,但是一经赋值,其类型也就固定了。Ref<a’>类型暴露了Value属性,我们也可以通过它来获取或设置ref类型的值。

F# Code
let phrase = ref "Inconsistency"


考虑一个简单的问题,求一个整型数组所有元素的和。先看C#怎么做:

复制代码
C# Code
static int TotalArray(int[] array)
{
int total = 0;
foreach (int element in array)
{
total
+= element;
}

return total;
}
复制代码


再看看F#的版本:

复制代码
F# Code
let totalArray (intArray : int array) =
let x = ref 0
for n in intArray do
x := !x + n
!x
复制代码


可以看到F#的命令式编程范式下与C#何其相似!

数组(Array) 

数组算得上是我们最熟悉的数据结构了。F#中的数组基于BCL中的System.Array类型,是一种可修改的集合类型。数组与列表相对,数组中的值是可修改的,而列表中的值则不能;列表的容量(长度)可以动态增大,数组则不能。一维数组又时被称为向量(Vector),多维数组有时被称为矩阵(Matrix)。定义数组时,将各项置于“[|”和“|]”中,各项间用“;”隔开。

下面的例子演示了如何对数组进行读取和写入操作。

复制代码
F# Code
// 定义
let rhymeArray = [| "Hello"; "F#" |]

// 读取
let firstPiggy = rhymeArray.[0]
let secondPiggy = rhymeArray.[1]

// 写入
rhymeArray.[0] <- "Byebye"
rhymeArray.[
1] <- "my friend"

// 输出
print_endline firstPiggy
print_endline secondPiggy
print_any rhymeArray
复制代码


数组跟列表一样,也采用了类型参数化,数组的类型为其元素的类型,因此rhymeArray的类型为string array,也可写作string[]。

F#中的多维数组可分为两类:交错数组(jagged array)和规则数组。交错数组,表示数组的数组,就是说最外部数组的元素也是数组(称为内部数组),内部数组的长度不必相同。而规则数组,事实上整个数组作为一个对象,其内部数组的长度是相同的。

先来看看交错数组的用法:

复制代码
F# Code
let jaggedArray = [| [| "one" |]; [| "two"; "three" |] |]
let singleDimension = jaggedArray.[0]
let itemOne = singleDimension.[0]
let itemTwo = jaggedArray.[1].[0]

printfn
"%s %s" itemOne itemTwo // one two
复制代码


jaggedArray的类型为string array array,这也是为什么说它是数组的数组,操作规则数组的语法有所不同:

复制代码
F# Code
let square = Array2.create 2 2 0
square.[
0,0] <- 1
square.[
0,1] <- 2
square.[
1,0] <- 3
square.[
1,1] <- 4
printf
"%A \r\n" square // [| [|1; 2|]; [|3; 4|] |]
复制代码


square的类型为int[,]。

注意:要编写.NET 1.1 和.NET 2.0兼容的代码,需要使用Microsoft.FSharp.Compatibility命名空间的CompatArray和CompatMatrix类。

数组推导(Array Comprehension) 

前面介绍过了关于列表和序列的推导语法。我们也可以使用类似的语法进行数组推导。

复制代码
F# Code
let chars = [|'1' .. '9'|]

let squares =
[
| for x in 1 .. 9
-> x, x * x |]
printfn
"%A" chars
printfn
"%A" squares
复制代码


注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译。

F#系列随笔索引页面

参考:
《Foundations of F#》 by Robert Pickering
《Expert F#》 by Don Syme , Adam Granicz , Antonio Cisternino
《F# Specs》


本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2008/09/25/fs-adventure-ip-part-one.html,如需转载请自行联系原作者。

相关文章:

Spring注解@Component和@resource的使用

http://heavengate.blog.163.com/blog/static/20238105320127234165489/转载于:https://www.cnblogs.com/passer1991/archive/2012/12/05/2802718.html

【青少年编程】黄羽恒:平行空间

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

软件测试培训分享:软件测试岗位面试技巧有哪些?

在参加完软件测试培训后&#xff0c;我们最先要准备的就是Python的面试工作了&#xff0c;任何面试都是有技巧的&#xff0c;软件测试也不例外&#xff0c;那么下面小编就为大家详细的介绍一下软件测试岗位面试技巧有哪些?希望可以帮助到大家。 软件测试培训分享&#xff1a;软…

【青少年编程】黄羽恒:Python

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

如何把文件隐藏在一张图片里面

该技巧适合 Windows 2000 / XP / Vista 1&#xff1a;准备一张图片&#xff0c;比如 jian.jpg。准备目标文件&#xff0c;比如dan.txt。我们要实现把dan.txt隐藏在图片里面。 2&#xff1a;打开命令行提示符&#xff1a;点击“开始→运行”&#xff0c;输入“cmd”。 3&#xf…

企业级nosql数据库应用与实战-redis

企业级nosql数据库应用与实战-redis项目场景&#xff1a;随着互联网2.0时代的发展&#xff0c;越来越多的公司更加注重用户体验和互动&#xff0c;这些公司的平台上会出现越来越多方便用户操作和选择的新功能&#xff0c;如优惠券发放、抢红包、购物车、热点新闻、购物排行榜等…

如何评判软件测试培训机构的好坏?

想要学习软件测试技术&#xff0c;那么找一家软件测试培训机构无疑是最好的选择&#xff0c;那么如今市面上的软件测试培训机构比较多&#xff0c;如何评判软件测试培训机构的好坏呢?来看看下面的详细介绍。 如何评判软件测试培训机构的好坏?现在国内的软件行业的市场日益增大…

Oracle 11g 新特性 -- Transparent Data Encryption (透明数据加密TDE) 增强 说明

一.TransparentData Encryption (TDE&#xff1a;透明数据加密) 说明Orace TDE 是Orcle 10R2中的一个新特性&#xff0c;其可以用来加密数据文件里的数据&#xff0c;保护从操作系统层面上对数据文件的访问。11g的TDE 在10gR2的基础上增强&#xff0c;允许在表空间级别进行加密…

[USACO07NOV]Cow Relays

mapfloyed矩阵乘法&#xff08;倍增floyed&#xff09; # include <stdio.h> # include <stdlib.h> # include <iostream> # include <algorithm> # include <string.h> # include <map> # define IL inline # define RG register # defin…

NCEPU:线下组队学习周报(011)

线下组队学习 经过一段时间的准备&#xff0c;我们组织的线下组队学习逐步进入正轨。欢迎华北电力大学保定校区的伙伴加入进来大家一起学习一起成长。 我们开展组队学习的内容为&#xff1a; &#xff08;1&#xff09;周志华的《机器学习》&#xff08;西瓜书&#xff09; …

java培训分享:学习java开发的优势是什么

想要进入到互联网行业的小伙伴&#xff0c;经常比较纠结学那个学科比较好&#xff0c;目前java、web前端、Python等都是非常热门的行业&#xff0c;前景也是比较好的&#xff0c;选择java学科的人比较多&#xff0c;那么学习java开发的优势是什么呢?来看看下面的详细介绍。 ja…

简述 clearfix 的原理

Demo Demo中的CSS中用到这样一个样式: .clearfix:after{content: ;display: block;clear: both; 复制代码 该样式通过::after选择器在选择元素之后插入content: 单引号中的内容&#xff0c;该内容不可被选定&#xff0c;例子中中插入内容为空。对这个空元素添加clear:both&…

判断dataset是否为空

最近在做一个搜索功能的时候,发现当用户搜索某些内容的时候,如果数据库有数据,就会显示在页面上.但是没有的时候呢,就一片空白,没有任何提示,觉得界面不友好.但是一时又不知道怎么判断dataset为空.网上找了一下,所以在这里写一下,方便自己以后复查.其实判断为空很简单,就这么写…

【Whalepaper】CV论文研读 - You Only Look One-level Feature (YOLOF), CVPR2021

Whalepaper是由周郴莲负责的一个每周分享论文的活动&#xff0c;带你研读AI领域的论文&#xff0c;快来一起开源学术科研吧&#xff01; NLP 论文分享&#xff1a;每周日 晚上 九点CV 论文分享&#xff1a; 每周日 晚上 九点Res 论文分享&#xff1a;每周六 晚上 九点半 欢迎…

Python培训分享:PyQT是什么?PyQt4和PyQt5的区别是什么?

今天小编为大家介绍的课程是关于Python培训方面的教程&#xff0c;主要讲的是PyQT是什么?PyQt4和PyQt5的区别是什么?来看看下面的详细介绍吧。 Python培训分享&#xff1a;PyQT是什么?PyQt4和PyQt5的区别是什么? QT是跨平台C库的集合&#xff0c;它实现高级API来访问现代桌…

linux 自学系列: 常用文件结构

/bin 二进制可执行命令  /dev 设备特殊文件  /etc 系统管理和配置文件  /etc/rc.d 启动的配置文件和脚本  /home 用户主目录&#xff0c;比如用户user的主目录就是/home/user&#xff0c;可以用&#xff5e;user表示  /lib 标准程序设计库&#xff08;动态链接共享库…

【青少年编程】【三级】海底寻宝

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

iOS更改AppIcon

前言 iOS 10.3出了挺多的新功能&#xff0c;其中有一个就是可以自定义更改AppIcon。这个功能以前我们只能在苹果的时钟上面看出来。但是一直没有对外开放。现在开放出来&#xff0c;也算是一种突破。 实现 苹果在iOS 10.3中&#xff0c;多了一个setAlternateIconName:completio…

软件测试培训分享:做软件测试需要掌握数据库的知识吗?

最近几年&#xff0c;学习软件测试的同学越来越多&#xff0c;大家对于“做软件测试需要掌握数据库的知识吗?”这个问题都比较关注&#xff0c;那么下面小编就来为大家做下详细的介绍。 软件测试培训分享&#xff1a;做软件测试需要掌握数据库的知识吗?我们知道做开发、做运维…

enumeration学习

import org.junit.Test;public class enumeration { Test public void test() { System.out.print(School.name"--> "); System.out.print(School.name.getvalue()" :"); System.out.println(School.name.info()); }}enum School { name("名字&quo…

Cocoa pods的安装

使用下面命令不能移除原生的SOURCESgem sources --remove https://rubygems.org/报错如下source https://rubygems.org/ not present in cache究其原因&#xff0c;可是我上次更新的时候中断了解决方法&#xff0c;少敲一个/(这是什么鬼)gem sources --remove https://rubygems…

【青少年编程】黄羽恒:漫天飞雪

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

学习软件测试发展前景怎么样?有前途吗?

学习软件测试发展前景怎么样?有前途吗?很多人都比较关心这个问题&#xff0c;因为最近要学习软件测试技术的同学越来越多&#xff0c;大家对于今后的工作都比较迷茫&#xff0c;那么针对这个问题&#xff0c;来看看下面的详细介绍就知道了。 学习软件测试发展前景怎么样?有前…

Hello Jsilk

我们选择这里作为学习和分享技术知识的一个平台&#xff0c;希望大家共同进步&#xff01; 1 (function(){ 2 alert(Hello Jsilk); 3 })(); 转载于:https://www.cnblogs.com/jsilk/archive/2012/12/10/2811255.html

js 使用 Lawnchair 存储 json 对象到本地

项目中用到储存点击记录的功能。 Lawnchair是一个轻量级的移动应用程序数据持久化存储方案&#xff0c;同时也是客户端JSON文档存储方法&#xff0c;优点是短小&#xff0c;语法简洁&#xff0c;扩展性比较好。 现在做HTML5移动应用除了LocalStorage的兼容性比较好之外&#xf…

邓林权:组队学习的那些事

自我介绍 大家好&#xff0c;我是邓林权&#xff0c;职业是数据营销&#xff0c;工作是以数据为主&#xff0c;通过数据进行决策。平时喜欢研究数据&#xff0c;领略数据的魅力&#xff0c;很荣幸能够和大家分享我参加组队学习的收获和故事。 组队学习经历 今年4月开始参加组…

软件测试培训分享:软件测试初期怎么面试工作?

学会了软件测试技术&#xff0c;那么大家后面接下来的准备工作就是面试找工作了&#xff0c;对于新手来说&#xff0c;面试软件测试这个岗位也是要有一些技巧的&#xff0c;下面小编就为大家介绍一下软件测试新手怎么面试工作? 软件测试培训分享&#xff1a;软件测试新手怎么面…

MySQL的正则表达式

1. Mysql的正则表达式仅仅使SQL语言的一个子集&#xff0c;可以匹配基本的字符、字符串。 例如&#xff1a;select * from wp_posts where post_name REGEXP hello&#xff0c;可以检索出列post_name中所有包含hello的行 REGEXP .og .是正则表达式中里一个特殊的字符。它表示…

Datawhale组队学习周报(第020周)

文章目录已经结营的开源内容即将结营的开源内容正在进行的开源内容七月排期的开源内容每周号外Whalepaper 招新公告本周&#xff08;06月21日~06月27日&#xff09;&#xff0c;第 25 期组队学习一共有 3 门开源课程&#xff0c;共组建了 3 个学习群&#xff0c;参与的学习者有…

EffectKeyMap系列1(Ubuntu)

说在前面 相信大多数人都会有这样的体验。当双手在键盘上一直打字或者写代码的时候&#xff0c;一次鼠标操作会让你崩溃。尤其是频繁使用鼠标的时候&#xff0c;工作效率会大幅下降。所以为了提高工作效率&#xff0c;我希望尽量少的使用鼠标。 以下我会总结我使用较高的几款软…