是时候介绍如何在F#中定义函数了,在你没有接触过函数式编程语言之前,你也许会觉得C#/Java的语法已经够丰富了,有什么任务做不了呢?当你读过函数式编程之Currying函数式编程之Partial application,你就会发现C#在函数式编程方面已经略显无力了,虽然我用C#模拟了这两种函数式特性,但是实现价值已经不大了,也许你从来没见到过有人这样用C#,因为对于C#而言就是OO范式,只是借鉴了少数函数式特性而已,比如LINQ。

当然写这篇博客的目的除了让西安的.NET社区发展壮大,让更多的人加入我们,一起参加线下分享,对我自己而言我是有私心的,那就是把整个函数式编程思想捋一遍,如果我能讲明白说明我是理解的,对不明白的地方还会继续查阅书籍。

另外写博客的好处还在于我可以畅所欲言,其实我作为过来人很想给年轻人一些建议和指引。当大家都在热衷于学习各类关于分布式架构微服务的时候我其实是想告诉他们静下心来,特别当你还是一个经验不算丰富的开发者。作为一个还算对微服务/DDD/CQRS略有经验的人,我是不会轻易提出这些方案的。除非我的客户和交付团队具备了这样的能力,我才会安心的增加这种复杂度。我也会在未来写出诸如软件架构的十种风格之类的连载,但是当大家没搞明白这些架构风格的来龙去脉之前把时间花在上面会走很多弯路,有时候你觉得高不可攀的东西早在几十年前就有了,只是现在换个名字,换个框架而已。

反之我们在看了很多三五年,甚至是十年以上的来自候选人的作业,能够达到标准的寥寥无几。我们在考察候选人的代码风格,对事物的抽象能力。而你的代码随意命名,没有使用语言的任何新特性,没有关键的业务抽象,更不知道什么是SOLID 原则, 你跟我谈DDD?怎么可能。

定义函数

言归正传,看看用C#如何实现求平方和的功能:

public int SumOfSquare(int n)
{
var sum = Enumerable.Range(1, n)
.Select(x => x * x)
.Sum(); return sum;
} // 调用
var result = SumOfSquare(100)

这是一段用LINQ编写的声明式风格的代码,相比for loop而言,这段代码已经非常优雅了。

在F#是如何实现的呢?

// 定义一个求平方的函数square
let square x = x * x // 定义sumOfSquares函数
let sumOfSquares n =
[1..n]
|> List.map square
|> List.sum // 调用
sumOfSquares 100

符号|>被称作是管道符,它的作用是把第一个表达式的输出放入第二个表达式的输入,以此类推将一段函数连接起来。

上面的代码可以描述为:

  • 先定义一个求平方的函数sqaure
  • 把[1..n]生成的list放入List.map函数中,List.map的定义为:
('a -> 'b) -> 'a list -> 'b list

即接受一个'a -> 'b类型的任意函数,同时接受'a类型的list数据,最终返回`b类型的list数据,跟C#中的Select函数差不多。

  • 把List.map函数返回的数据放入 List.sum函数中

由于type inference的原因,F#几乎不用声明类型,另外也没有大括号和分号用来表示语句终止。F#采用了和Python类似的思路,用缩进来表示不同的作用域。

理解管道符 |>

上面的例子使用到了管道符|>,其实它并不是什么神秘的东西,他也是一个函数,只不过函数名就叫|>而已。

F#通过下面的代码来定义|>:

let (|>) x f = f x

用()括起来的函数名可以理解为运算符,例如+-,运算符是支持中缀表达式的,例如你可以这样使用:

2 |> string

其中2就是管道符定义中的x,string函数就是管道符定义中的f,最终等价于:

string 2

再来一个例子:

let add x y = x + y

2 |> add 3

其实就是:

add 3 2

定义能够做Partial application的函数

Partial application是指在调用函数式只提供部分参数,从而生成一个嵌入已知参数的新函数,这里面的关键在于函数的参数顺序。

let addWithPluggableLogger logger x y =
let result = x + y
logger "x+y" result
result

例如我们定义了一个具有logging能力的add函数,参数logger作为一个相对稳定的参数被设计到了第一个位置,比如你可以用consoleLogger做Partial application:

let addWithConsoleLogger = addWithPluggableLogger consoleLogger
addWithConsoleLogger 1 2

试想如果参数logger被设计到了其他的位置,可能就无法定义出addWithConsoleLogger这样的函数了。

那么到底如何设计函数的参数顺序呢?下面的指导原则可以供参考:

  1. 对于稳定的参数要设计在前面,因为你最有可能对相对稳定/固定的参数做Partial application
  2. 对数据类的参数放在后面,例如List库的大多函数List.mapList.minBy都是这样设计的:
List.map (fun i -> i + 1) [0;1;2;3]
List.minBy (fun i -> i + 1) [0;1;2;3]

基于这样的函数定义,你才可以通过管道符把对一个数据集合的操作连接起来:

let result =
[1..10]
|> List.map (fun i -> i+1)
|> List.filter (fun i -> i>10)

另外这样定义的函数还特别容易粘接和组合,下一篇将介绍如何组合函数。

函数式编程之-定义能够支持Partial application的函数的更多相关文章

  1. 函数式编程之-Partial application

    上一篇关于Currying的介绍,我们提到F#是如何做Currying变换的: let addWithThreeParameters x y z = x + y + z let intermediat ...

  2. Python3 函数式编程

    函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用.而允许使用变量的程序设计语言,由 ...

  3. Python进阶之函数式编程

    函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计 ...

  4. Scala函数与函数式编程

    函数是scala的重要组成部分, 本文将探讨scala中函数的应用. scala作为支持函数式编程的语言, scala可以将函数作为对象即所谓"函数是一等公民". 函数定义 sca ...

  5. Python进阶:函数式编程(高阶函数,map,reduce,filter,sorted,返回函数,匿名函数,偏函数)...啊啊啊

    函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计 ...

  6. 【Python】【函数式编程】

    #[练习] 请定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程: ax2 + bx + c = 0 的两个解. 提示:计算平方根可以调用math.sqrt()函数: & ...

  7. Python学习--08函数式编程

    函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数. 高阶函数 Python支持高阶函数(Higher-order function). 什么是高阶函数呢?把函数作为参 ...

  8. (转)Python进阶:函数式编程(高阶函数,map,reduce,filter,sorted,返回函数,匿名函数,偏函数)

    原文:https://www.cnblogs.com/chenwolong/p/reduce.html 函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数 ...

  9. python笔记三:函数式编程

    1.概念: 函数式编程就是一种抽象程度很http://i.cnblogs.com/EditPosts.aspx?opt=1高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要 ...

随机推荐

  1. 采用Google预训bert实现中文NER任务

    本博文介绍用Google pre-training的bert(Bidirectional Encoder Representational from Transformers)做中文NER(Name ...

  2. js 对象与json的转化

    1.将对象转换为JSON格式字符串 JSON.stringify(object) 2.将JSON字符串转换为对象 JSON.parse(jsonString);

  3. 查看进程被哪台电脑的哪个进程连接(netstat)

    1.netstat -ano|findstr 进程号,找出连接该进程A所有的ip(可以看到该进程的端口号和连接进程的端口号): 2.到连接该进程的电脑上,打开cmd命令行,输入netstat -ano ...

  4. 线程中的join方法

    join方法的作用是同步线程. 1.不使用join方法:当设置多个线程时,在一般情况下(无守护线程,setDeamon=False),多个线程同时启动,主线程执行完,会等待其他子线程执行完,程序才会退 ...

  5. python删除文件和文件夹

    python中删除文件:os.remove(path) path为文件的路径 import os os.remove(path) python中删除文件夹:shutil.rmtree(path) pa ...

  6. [译]使用explain API摆脱ElasticSearch集群RED苦恼(转)

    "哔...哔...哗",PagerDuty的报警通知又来了. 可能是因为你又遭遇了节点宕机, 或者服务器机架不可用, 或者整个ElasticSearch集群重启了. 不管哪种情况, ...

  7. squid 正向代理 简单配置

    linux 正向同步 项目上web服务器不给访问外网,迁移服务器环境又太麻烦,决定给web服务器做正向代理,刚开始使用nginx,但是https代理一直不成功,后面大佬建议使用squid来达到相同目的 ...

  8. 更改MySQL/Postgresql密码

    Parrot包括几个SQL引擎,但是当它们被预先安装时,默认密码未被配置,并且拒绝访问其root用户. 重新配置Mysql / Mariadb密码 停止MySQL服务. sudo service my ...

  9. JObject,JArray的基本操作

    引用:https://www.cnblogs.com/dacongge/p/6957074.html 1.JObject:基本的json对象 /// <summary> /// Gets ...

  10. XML学习总结一

    1.声明 <?xml version="1.0"  encoding="gb2312"?> version属性指定该文档遵守的版本号,通常为1.0X ...