如果你也会C#,那不妨了解下F#(4):了解函数及常用函数

函数式编程其实就是按照数学上的函数运算思想来实现计算机上的运算。虽然我们不需要深入了解数学函数的知识,但应该清楚函数式编程的基础是来自于数学。

例如数学函数f(x) = x^2+x,并没有指定返回值的类型,在数学函数中并不需要关心数值类型和返回值。F#代码为let f x = x ** 2.0 + x,F#代码和数学函数非常类似,其实这就是函数式编程的思想:只考虑用什么进行计算以及计算的结果(或者叫“输入和输出”),并不考虑怎样计算。

其实,你可以把任何程序看成是一系列函数,输入是你鼠标和键盘的操作,输出是程序的运行结果。你不需要关心程序是怎样运行的,这些函数会根据你的输入来输出结果,而其中的算法是以函数的形式,而不是类或者对象。

下面我们就先了解一些函数式编程中函数相关的东西。


了解函数

不可变性

在一个函数中改变了程序的状态(比如在文件中写入数据或者在内存中改变了全局变量)我们称为副作用。像我们使用printfn函数,无论输入是什么,返回值均为unit,但它的副作用是打印文字到屏幕上了。

副作用并不一定不好,但却经常是很多bug的根源。我们分别用命令式和函数式求一组数字的平方和:

let square x = x * xlet sum1 nums =    let mutable total = 0for i in nums do let x = square itotal <- total + xtotallet sum2 nums =    Seq.sum (Seq.map square nums)

sum2中使用了Seq模块中的函数,这些函数将在稍候进行介绍。

可以看出,函数式代码简短了许多,且少了很多变量的声明。而且sum1是顺序执行,若想以并行方式运行则需要更改所有代码,但sum2只需要替换其中的Seq.sumSeq.map函数。

函数和值

在我们接触到的非函数式编程语言(包括C#)中,函数和数值总是有一些不同。但在函数式编程语言中,函数也是值。比如,函数可以作为其他函数的参数,也可以作为返回值(即高阶函数)。而这在函数式编程中是非常常见的。
需要注意的是,我们叫“”而不叫“变量”。因为在函数式编程中声明的东西默认是不可变的。(在F#中不完全如此,是因为F#包含了面向对象编程范式,可以说并非纯函数式编程语言。)

我们看下面以函数作为参数的代码(求一组数字的负值):

> let negate x = -x;;
val negate : x:int -> int> List.map negate [1..5];;
val it : int list = [-1; -2; -3; -4; -5]

我们使用函数negate和列表[1..5]作为List.map的参数。

但很多时候我们不需要给函数一个名称,只需使用匿名函数或叫Lambda表达式。在F#中,Lambda表达式为:关键字fun和参数,加上箭头->和函数体。则上面的代码可以更改为:

List.map (fun i-> -i) [1..5];;

我们再看以函数作为返回值的例子,假设我们定义一个powOf函数,输入一个值,返回一个该值求幂的函数:

let powOf baseValue =(fun exp -> baseValue ** exp)let powOf2 = powOf 2.0  // f(x) = 2^xlet powOf3 = powOf 3.0  // f(x) = 3^xpowOf2 8.               // 256.0powOf3 8.               // 6561.0

其中powOf2即为powOf函数使用参数2返回的函数。其实这里涉及到闭包的内容,就不详细解释了,我们详细函数式编程时可能会再提及。

递归

递归大家都熟悉,只是在F#中声明时,需要添加rec关键字:

let rec fact x =    if x <= 1 then 1else x * fact (x-1)
fact 5;;(*    val fact : x:int -> int    val it : int = 120 *)

其实需要显示声明递归是因为F#的类型推断系统无法在函数声明完成之前确定其类型,而使用rec关键字后,就允许在确定类型前调用该函数。

部分函数:Partial Function

在函数式编程中,还有一个叫Partial Function(暂且叫部分函数吧)的,可以把接收多个参数的函数分解成接收单个参数,即柯里化(Currying)

我们知道,使用函数printfn打印整数的语句为printfn "%d" i,我们定义一个打印整数的函数:

> let printInt i = printfn "%d" i;;
val printInt : i:int -> unit
> let printInt = printfn "%d";;
val printInt : (int -> unit)

符号函数

在F#中,如+ - * /等运算符其实属于内建函数。而我们也可以使用这些符号来自定义符号函数。

我们用符号来重新定义上面的阶乘函数:

let rec (!) x =    if x <= 1 then 1else x * !(x - 1)
!5;;(*    val ( ! ) : int -> int    val it : int = 120 *)

需要注意的是,符号函数一般需要括号包裹,如果符号函数的参数不止一个,则符号函数是以中缀的方式来使用,例如我们用=~=定义一个验证字符串是否和正则表达式匹配的函数:

open System.Text.RegularExpressions;;let (=~=) str (regex : string) =Regex.Match(str, regex).Success"The quick brown fox" =~= "The (.*) fox";;
(*val ( =~= ) : string -> string -> boolval it : bool = true*)

而且,符号函数也可以作为高阶函数的参数

管道符:|><|

我们再返回来看上面的平方和函数:

let sum2 nums =    Seq.sum (Seq.map square nums)

假如函数层次非常多,一层包裹一层,则可读性非常差。

在F#定义了如下符号函数

let (|>) x f = f xlet (<|) f x = f x

我们称为“正向管道符”和“逆向管道符”。则上面的平方和函数可写作:

let sum2 nums = nums |> Seq.map square |> Seq.sum

<|虽然用得不多,但常用来改变优先级而无需使用括号:

let sum2 nums =    Seq.sum <| Seq.map square nums

合成符:>><<

我们也可以用函数合成符将多个函数组合成一个函数,合成符也分正向(>>)和逆向(<<)。

let (>>) f g x = g(f x)
let (<<) f g x = f(g x)

还是以上面的求平方和为例(Seq.map square即是一个部分函数):

let sum2 nums = (Seq.map square >> Seq.sum) numslet sum2 nums = (Seq.sum << Seq.map square) nums

常用模块函数

在上一篇中,我们了解了集合类型。在F#中,为这些集合类型定义了许多函数,分别在集合名称对应的模块中,例如Seq的相关函数位于模块Microsoft.FSharp.Collections.Seq中。而这也是我们最常用到的模块。

模块(module是F#中组织代码的一种方式,类似于命令空间(namespace)。但F#中也是有命名空间的,其间的区别将在下一篇介绍。

下面简单介绍常用的函数,并会列出与.Net的System.Linq中对应的函数。

如无特别说明,该函数在三个模块中均可用,但因为集合的实现方式不同,函数的复杂度也会有区别,在使用中根据实际情况选择合适的函数。

length

对应于Linq中的Count。即获得集合中元素的个数

[1..10] |> List.length;;    // 10Seq.length {1..100};;       // 100

虽然在Seq中也有length函数,但谨慎使用,因为Seq可能为无限序列。

exists 和 exists2

exists用于判断集合是否存在符合给定条件的元素,对应于Linq中的Any。而exists2用于判断两个集合是否包含在同一位置且符合给定条件的一对元素。

List.exists ((=) 3) [1;3;5;7];;     //trueSeq.exists (fun n1 n2 -> n1=n2) {1..5} {5..-1..1};; //true

第一行代码判断列表中是否包含等于3的元素,其中(=) 3即为部分函数,注意=为符号函数。

第二行代码判断两个序列中,因为{1;2;3;4;5}{5;4;3;2;1}在索引2的位置存在元素符合函数(fun n1 n2 -> n1=n2),所以返回true

forall 和 forall2

forall检查是否集合中所有元素均满足指定条件,对应Linq中的All

let nums = {2..2..10}
nums |> Seq.forall (fun n -> n % 2 = 0);;   //true

forall2exists2类似,但当且仅当所有元素都满足相同位置且符合给定条件才返回true。接上一个代码片段:

let nums2 = {12..2..20}
Seq.forall2 (fun n n2 -> n + 10 = n2) nums nums2;;  //true

find 和 findIndex

find查找符合条件的第一个元素,对应Linq中的First。需要注意的是当不存在符合条件的元素,将引发KeyNotFoundException异常。

Seq.find (fun i -> i % 5 = 0) {1..100};;    //5

findIndex则返回符合条件的第一个元素的索引

map 和 mapi

map对应Linq中的Select,将函数应用于集合中的每个元素,返回值产生一个新的集合。

List.map ((*) 2) [1..10];;  // [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]

mapimap类似,不过在应用的函数中还需要传入一个整数作为集合的索引。

Seq.mapi(fun i x -> x*i) [3;5;7;8;0];; 
// 将各个元素乘以各自的索引,结果为:[0; 5; 14; 24; 0]

iter 和 iteri

iter将函数应用于集合中的每个元素,但函数返回值为unit。功能类似于for循环。
iterimapi一样需要在函数中传入一个索引。

Seq.iteri(fun i x -> printfn "第%d个元素为:%d" i x) [3;5;7;8;0]
(*第0个元素为:31个元素为:5……
*)

filter 和 where

F#中filterwhere是一样的,对应于Linq中的Where。用于查找符合条件的元素。

{1..10} |> Seq.filter (fun n -> n%2 = 0);;//val it : seq<int> = seq [2; 4; 6; 8; ...]

fold

fold对应Linq中的Aggregate,通过提供初始值,然后将函数逐个应用于每个元素,返回单一值。

Seq.fold (fun acc n -> acc + n) 0 {1..5};;  //15Seq.fold (fun acc n -> acc + string n) "" {1..10};; 
//"12345"

首先,将初始值与第一个元素应用于函数,再将返回值与第二个元素应用于函数,依此类推……

Linq中的Aggregate包含不需要提供初始值的重载,其实F#中也有对应的reduce函数。类似的还有foldBackreduceBack等逆向操作,这里就不介绍了。

collect

collect对应Linq中的SelectMany,展开集合并返回所有二级集合的元素。

let lists = [ [0;1]; [0;1;2]; [0;1;2;3] ]
lists |> List.collect id;;
//[0; 1; 0; 1; 2; 0; 1; 2; 3]

其中idOperators模块中的函数,它的实现为fun n->n,即直接对参数进行返回。

append

append将两个集合类型合并成一个,对应于Linq中的Concat

> Array.append [|1;3;1;4|] [|5;2;0|];;
val it : int [] = [|1; 3; 1; 4; 5; 2; 0|]

zip 和 zip3

zip函数将两个集合合并到一个里,合并后每个元素是一个二元元组。

let list1 = [ 1..3 ]
let list2 = [ "a";"b";"c" ]List.zip list1 list2;;
// [(1, "a"); (2, "b"); (3, "c")]

zip3顾名思义,就是将三个集合合并到一个里。

合并后的长度取决于最短的集合的长度。

rev

rev函数反转一个列表或数组,在Seq模块中没有这个函数。

sort

sort函数基于compare函数(第二篇中的“比较”介绍过)对集合中的元素进行排序。

> List.sort [1;3;-2;2];;
val it : int list = [-2; 1; 2; 3]

数学函数

Linq中包含MaxMinAverageSum等数学函数。F#集合模块中也有对应的函数。

List.max [1..10]        //10Seq.min {1..5}          //5[1..10] |> List.map float |> List.average   //5.5List.averageBy float [1..10]                //5.5[0..100] |> Seq.where (fun x -> x % 2 <> 0) |> Seq.sum |> printf "0到100中的奇数的和为%i"// 0到100中的奇数的和为2500

需要注意的是,average函数需要集合中的元素支持精确除法(Exact division,即实现了DivideByInt函数的类型。不知道为什么是ByInt。),而F#中又不支持隐式类型转换,所以对int集合求平均值只能先转换为floatfloat32,或使用averageBy函数。

sum函数的示例代码将第一篇中由C#翻译过来的命令示示例代码转换成了函数式的代码。

集合间转换

三种集合类型的对应模块中,均提供转换到(to)另外两种集合类型,和从(of)另外两种类型转换的函数。

如Seq模块,通过Seq.toListSeq.toArray函数转出;通过Seq.ofListSeq.ofArray转入。

Seq.toList {1..5};;         //[1; 2; 3; 4; 5]List.ofArray [|1..5|];;     //[1; 2; 3; 4; 5]

函数式编程,核心就是函数的运用。上面介绍的这些在C#中也经常使用到对应的方法,但F#提供的函数非常丰富,大家可通过MSDN了解更多:

  • Seq模块

  • List模块

  • Array模块

因为F#中的List和Array均实现了IEnumarable<T>接口,所以Seq模块的函数也可以接收List类型和Array类型的参数。当然,反之则不行。

到现在为止,我们了解的F#都是在交互窗口中。下一篇我们再简单介绍项目创建和代码组织,即模块相关。

相关文章:

  • 如果你也会C#,那不妨了解下F#(1):F# 数据类型

  • 如果你也会C#,那不妨了解下F#(2):数值运算和流程控制语法

  • 如果你也会C#,那不妨了解下F#(3):F#集合类型和其他核心类型

  • 【送书活动】机器学习项目开发实战

  • 《机器学习项目开发实战》送书活动结果公布

原文地址:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-4.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328654.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新闻发布项目——实体类(newsTb)

package bdqn.newsMange.entity;import java.util.Date;/*** 新闻的实体类* author Administrator**/ public class newsTb {private int newsID;// 编号private int categoryID;//类别编号(外键)private String summary;// 摘要private String title;//题目private String con…

Java URL协议扩展实现

转载自 Java URL协议扩展实现在信息交互系统设计中&#xff0c;不乏有自定义通讯协议设计。本章会介绍如何利用 java.net.URL 类来自定义协议。一般而言&#xff0c; URL 的格式是&#xff1a; protocol://[authority]hostname:port/resource?queryString 。 URL 类能够解析…

axure8 事件改变样式_AxureRP8实战手册-案例2(文本框:边框变色)

案例2. 文本框&#xff1a;边框变色案例来源:百度-登录界面案例效果&#xff1a;光标进入文本框时&#xff1a;(图1-6)案例描述&#xff1a;在登录界面中&#xff0c;包含用户名与密码的输入框。当焦点进入输入框时&#xff0c;输入框边框与内部图标变为蓝色&#xff1b;失去…

URL的getFile()和getPath()方法的区别

转载自 URL的getFile()和getPath()方法的区别 import java.net.MalformedURLException; import java.net.URL; public class dd { /** * param args */ public static void main(String[] args) { try { URL url new URL("file://ftp.yoyodyne.com/pub/files/fo…

webpack打包发布

npm run dev npm run dist --线上 git status git add . git commit -am "online adapter" git push git pull git branch --查看当前分支是在哪里master 还是 admin yarn install v1.3.2 yarn run dist -- 打包 fe-deply.sh --shell脚本 自动发布脚本 gi…

介绍开源的.net通信框架NetworkComms

Networkcomms 是一款C# 语言编写的TCP/UDP通信框架 作者是英国人 以前是收费的 目前作者已经开源 开源地址是:https://github.com/MarcFletcher/NetworkComms.Net networkcomms通讯框架使用简单方便,性能稳定. 从github上下载到源码后,需要Visual studio安装nuget 加载相关D…

dplayer js控制 自动全屏_vue-video-player 通过自定义按钮组件实现全屏切换效果【推荐】...

最近公司的产品上线&#xff0c;一些高级功能在基础版本中不对用户开发&#xff0c;通过视频的形式展示。产品开发用的是 vue, 经同事介绍使用了vue-video-player视频播放插件&#xff0c;通过 demo案例很快实现了视频播放效果class"vjs-custom-skin"ref"videoP…

新闻发布项目——实体类(comment)

package bdqn.newsMange.entity;import java.util.Date;/*** 新闻评论表*/ public class comment {private int commentID;//编号private String cmmUser;//用户名private String ip;//ipprivate String cmmContent;//内容private int newsID;//新闻IDprivate Date cmmDate;//…

java字符串格式化:String.format()方法的使用

转载自 java字符串格式化&#xff1a;String.format()方法的使用常规类型的格式化 String类的format()方法用于创建格式化的字符串以及连接多个字符串对象。熟悉C语言的读者应该记得C语言的sprintf()方法&#xff0c;两者有类似之处。format()方法有两种重载形式。 l for…

java web每天定时执行任务(四步轻松搞定)

https://www.cnblogs.com/LiSheng97625/p/4648739.html java web每天定时执行任务&#xff08;四步轻松搞定&#xff09; 第一步&#xff1a; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44…

从Unity3D编译器升级聊起Mono

接前篇Unity 5.3.5p8 C#编译器升级&#xff0c;本文侧重了解一些Mono的知识。 Unity3D的编译器升级 新升级的Mono C#编译器&#xff08;对应Mono 4.4&#xff09; Unity编辑器及播放器所使用的Mono运行时还未升级。 新编译器仍针对C# 4&#xff0c;是旧的编译器也支持的版本。 …

bat脚本中获取上级目录_批处理bat命令--获取当前盘符和当前目录和上级目录

批处理命令获取当前盘符和当前目录%~d0 是当前盘符%cd% 是当前目录可以用echo %cd%进行打印测试以下例子是命令行编译Visual Studio编写的程序&#xff1a;echo offset b%cd% //将当前目录保存到参数b中,等号前后不要有空格C:cd program filescd microsoft visual studiocd com…

日期类的加减及java中所以日期类的操作算法大全

转载自 日期类的加减及java中所以日期类的操作算法大全1.计算某一月份的最大天数Calendar timeCalendar.getInstance(); time.clear(); time.set(Calendar.YEAR,year); //year 为 int time.set(Calendar.MONTH,i-1);//注意,Calendar对象默认一月为0 int daytime.getActu…

使用Spring的@Scheduled实现定时任务

https://blog.csdn.net/prisonbreak_/article/details/49180307 使用Spring的Scheduled实现定时任务 2015年10月16日 16:23:21 阅读数&#xff1a;103485更多 个人分类&#xff1a; Java开发 Spring配置文件xmlns加入 xmlns:task"http://www.springframework.org/sch…

新闻发布项目——实体类(categoryTB)

package bdqn.newsMange.entity; /*** 新闻类别实体类* author Administrator**/ public class categoryTB {private int categoryID;//编号private String categoryName;//类别名public int getCategoryID() {return categoryID;}public void setCategoryID(int categoryID) {…

.NET Core 系列5 :使用 Nuget打包类库

NuGet是个开源项目&#xff0c;项目包括 NuGet VS插件/NuGet Explorer/NuGetServer/NuGet命令行等项目&#xff0c;.NET Core项目完全使用Nuget 管理组件之间的依赖关系&#xff0c;Nuget已经成为.NET 生态系统中不可或缺的一个组件&#xff0c;从项目角度&#xff0c;将项目中…

foxmail 不知道这样的主机_华为P50真机图!网友:早知道这样,就不加价买mate40了...

hello&#xff0c;大家好&#xff0c;欢迎收看本期节目由于各方面原因&#xff0c;今年的华为mate40系列确实很难抢不少心急的用户甚至是加价买了入手之后有的用户有开始后悔了&#xff0c;当然不是该机不好而是他们看到了疑似华为P50的真机渲染图它的正面并没有丑陋的双打孔&a…

F#年度调查结果概述

本文要点 年度社区调查向600多名开发人员了解了他们的F#使用情况&#xff1b;在工作中&#xff0c;F#的最大好处包括正确性、让不合法状态不可达以及能够更轻松地解决复杂的问题&#xff1b;大多数F#开发人员仍然使用Windows作为他们的开发平台之一&#xff0c;但使用Mac和Linu…

java Date获取 年月日时分秒

转载自 java Date获取 年月日时分秒 package com.util;import java.text.DateFormat; import java.util.Calendar; import java.util.Date;public class Test {public void getTimeByDate(){Date date new Date();DateFormat df1 DateFormat.getDateInstance();//日期格式&…

anroid 内存溢出 Bitmap OutOfMemoryError

Bitmap OutOfMemoryError 2014年06月09日 09:55:28 阅读数&#xff1a;2434 标签&#xff1a; OutOfMemoryErrorBitmap回收 更多 个人分类&#xff1a; 异常问题和解决办法 在创建Bitmap的时候由于对象过多而没有即使回收&#xff0c;导致的内存不足&#xff1a; java.lang…