是时候介绍如何在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. 计算机爱好者协会技术贴markdown第三期

    之前都是给大家展示的对文本的基本操作,这一期我们要给大家展示的就是很高大上的东西了哦 看左边是不是超low,右边是不是瞬间高大上了!!! 一个简单的表格是这么创建的: 爱酱     | Value - ...

  2. VS2017 带参数启动调式程序

    有些程序,比如FFPlay,需要传递命令行参数才能运行想要的功能,比如字幕, ffplay -vf subtitles=mv.mkv mv.mkv 参数是 -vf subtitles=mv.mkv m ...

  3. Latex引用\ref

    在等式/图/定理等后面加\label{name} 正文引用方法:等式使用 \eqref{name},非等式引用可使用\ref{name}.

  4. String工具类

    String工具类 问题描述 MAVEN依赖 代码成果 问题描述 很多时候我们需要对字符串进行很多固定的操作,而这些操作在JDK/JRE中又没有预置,于是我们想到了apache-commons组件,但 ...

  5. C#HTTP请求之POST请求和GET请求

    POST请求 /// <summary> /// POST请求获取信息 /// </summary> /// <param name="url"> ...

  6. python csv读写

    https://blog.csdn.net/taotiezhengfeng/article/details/75577998

  7. 回头来学习wpf的FlowDocument

    学习了一段时间的electron,其实是一个神奇的开发方式,让人神往.但其打印和ocx或是activeX方面还是让我不大放心去使用.毕竟当前首要任务还是window的应用开发. 于是重新学习wpf的F ...

  8. entity framework 上下文对象跟踪相关

    entity framework 上下文对于对象的跟踪有2中方式进行控制,第一种从数据库查询但不加载到上下文. 这里可以用到.AsNoTracing()方法. 这里用到的是实体(entity)在上下文 ...

  9. java web 开发手册

    W3School离线手册(2017.03)               提取密码: b2fo JavaScript高级程序设计第三版             提取密码: cscv CSS4.2.4 参 ...

  10. Codeforces831A Unimodal Array

    A. Unimodal Array time limit per test 1 second memory limit per test 256 megabytes input standard in ...