是时候介绍如何在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. 使用ajax实现html页面产品详情页文字具体内容

    <script type="text/javascript" src="Assets/js/jquery.min.js"></script&g ...

  2. 别人的Linux私房菜(20)启动流程、模块管理与Loader

    系统启动时,首先加载BIOS,通过BOIS读取COMS的硬件信息,进行自我检测,取得第一个可启动的设备(多个根据设置有关). 读取并执行设备内的MBR启动引导程序,引导程序调用boot sector中 ...

  3. 2019.03.25 bzoj2329: [HNOI2011]括号修复(fhq_treap)

    传送门 题意简述: 给一个括号序列,要求支持: 区间覆盖 区间取负 区间翻转 查询把一个区间改成合法括号序列最少改几位 思路: 先考虑静态的时候如何维护答案. 显然把所有合法的都删掉之后序列长这样: ...

  4. npm遇到的问题--npm install 执行报错 /bin/git submodule update -q --init --recursive

    1.执行npm i 安装依赖时,报错:cannot read property 'match' of undefined 据说是npm本地缓存导致 解决方案: rm -rf package-lock. ...

  5. 与我们息息相关的internet服务(3)---电子邮件服务

    几年前了解了一下,现在再实施的时候,再了解,当然如果要到牛人张小龙28岁时的开发程度,可能还差一个筋斗云 在起步一个公司,从组建的技术上,可能要准备很多东西,其中一个就是我们熟悉的企业邮箱. 伊妹儿, ...

  6. ASCII、Unicode、UTF-8以及Python3编码问题

    编码问题,其实的确是个很烦人的问题,一开始觉得不需要看,到后来出现问题,真的是抓狂, 而像我们这些刚刚涉及到这些问题的小白来说,更是无从下手,所以查阅资料,总结理解下各个概念以及Python3的编码问 ...

  7. 记录一次Service被注入mapper实例的错误

    在一个搭建框架为SSM的项目中,有一个需求是数据库更新同步Solr索引库的数据. 在使用ActiveMQ作为中间件,实现这个需求时却发生了一个错误. 在Listener实现类里我想注入一个Servic ...

  8. Leetcode_5.最长回文子串

    最长回文子串 题目描述: 给定一个字符串 s,找到 s 中最长的回文子串.你可以假设 s 的最大长度为 1000. 示例 1: 输入: "babad" 输出: "bab& ...

  9. Linux系统安装 OpenSSL两种方法

    OpenSSL是一个开源的ssl技术,由于安装pytbull,需要安装openssl,并下载对应的版本下载地址:https://www.openssl.org/source/ 方法一,编译安装Open ...

  10. RabbitMQ in Action(5): Clustering and dealing with failure

    Batteries included: RabbitMQ clustering The clustering built in to RabbitMQ was designed with two go ...