成为一名函数式码农(3)

前情提要:成为一名函数式码农(1),成为一名函数式码农(2)

函数组合(Function Composition)

作为码农,我们都很懒。我们不想一遍又一遍的编译、测试、部署我们已经写过的代码。

我们总是尝试找到一种方法可以只编写一次代码然后怎么复用它来做别的事情。

代码复用听起来很好但是很难实现。代码太具体就无法复用。太通用第一次很难使用。

所有我们需要在这两者间做一个平衡,寻求创建一些更小、可复用的代码片段的方式,这些代码片段可以作为构建更复杂功能的砖块。

在函数式编程中,函数就是我们的砖块。我们编写一些可以完成非常具体任务的函数,然后像乐高积木一样将他们搭建起来。

这就是所谓的函数组合

那么它是怎么工作的?我们以两个JavaScript函数开头:

var add10 = function(value) {
    return value + 10;
};
var mult5 = function(value) {
    return value * 5;
};

这太啰嗦了,让我们用箭头函数(fat arrow)表示法重写:

var add10 = value => value + 10;
var mult5 = value => value * 5;

这样好一些。现在想象一下,我们需要一个函数接收一个参数,将该参数加10然后乘以5。我们可以这样写:

var mult5AfterAdd10 = value => 5 * (value + 10)

尽管这是一很简单的例子,我们也不想从0开始编写这个函数。首先我们可能会出错,比如忘记括弧。

其次我们已经有一个函数可以加10,以及另外一个函数可以乘以5。我们在重复我们已经写过的代码。

所以取而代之,让我们使用add10mult5来创建新函数:

var mult5AfterAdd10 = value => mult5(add10(value));

我们仅仅使用现有的函数来创建mult5AfterAdd10,但是还有更好的方式。

在数学中,f ∘ g是复合函数,读作“f composed with g”,或者更常用的,“f after g”。 所以(f ∘ g)(x)等同于给定参数x调用g之后调用f,或者简单的说f(g(x))

在我们的例子中,有mult5 ∘ add10或者说“mult5 after add10”,所以函数的名称是mult5AfterAdd10

这个函数名也准确的说明了我们做的事情。我们先调用了 add10 然后调用 mult5,参数是value。 或者:mult5(add10(value))

由于JavaScript本身不支持组合函数,我们来看Elm中的实现:

add10 value =
    value + 10
mult5 value =
    value * 5
mult5AfterAdd10 value =
    (mult5 << add10) value

<<插入运算符是Elm语言中组合函数的方式。它直观得告诉我们数据的流向。首先,value传递给add10, 然后结果被传递给mult5

注意 mult5AfterAdd10中的括弧,即(mult5 << add10),它们确保在接收参数value之前先组合函数。

你也可以像这样按照你的需要组合任意多的函数:

f x =
   (g << h << s << r << t) x

这里 x被传递给函数t,其结果被传递给r,结果再传递个s。。。如果用JavaScript实现应该是这个样子g(h(s(r(t(x))))),括号的噩梦。

Point-Free Notation

Point-Free Notation就是在编写函数时不需要指定参数的编程风格。一开始,这风格看起来有点奇怪,但是随着不断深入,你会逐渐喜欢这种简洁的方式。

multi5AfterAdd10中,你会注意到value被指定了两次。一次在参数列表,另一次是在它被使用时。

-- This is a function that expects 1 parameter
mult5AfterAdd10 value =
    (mult5 << add10) value

但是这个参数不是必须的,因为该函数组合的最右边一个函数也就是add10期望相同的参数。下面的 point-free 版本是等效的:

-- This is also a function that expects 1 parameter
mult5AfterAdd10 =
    (mult5 << add10)

使用 point-free 版本有很多好处。

首先,我们不需要指定冗余的参数。由于不必指定参数,所以也就不必考虑为它们命名。

其次,由于更简短使得更容易阅读。本例比较简单,想象一下如果一个函数有多个参数的情况。

乐园的麻烦

到目前为止,我们已经了解了函数组合如何工作以及如何通过point-free风格使函数简洁、清晰、灵活。

现在,我们尝试将这些知识应用到一个稍微不同的场景。想象一下我使用add来替换add10

add x y =
    x + y
mult5 value =
    value * 5

在只有这两个函数的情况下我们怎么实现mult5After10?

继续阅读之前思考一下。不用很严肃,考虑一下。尝试实现它。

好的,如果你真的花时间思考了,你也许会给出类似这样的解决方法:

-- This is wrong !!!!
mult5AfterAdd10 =
    (mult5 << add) 10

但是这个无法运行。为什么?因为add需要两个参数。

如果在Elm语言中这还不明显的话,尝试将这一段代码用JavaScript实现:

var mult5AfterAdd10 = mult5(add(10)); // this doesn't work

这段代码是错误的,但是为什么?

因为这里add函数只能获取到两个参数(它的函数定义中指定了两个参数)中的一个(实际只传递了一个参数),所以它会将一个错误的结果传递给mult5。这最终会产生一个错误的结果。

事实上,在Elm中,编译器不允许你写出这种格式错误的代码(这是Elm的妙处之一)。

我们再试一次:

var mult5AfterAdd10 = y => mult5(add(10, y)); // not point-free

这个不是point-free风格但是我觉得还行。但是现在我不再仅仅组合函数。我在写一个新函数。同样如果这个函数更复杂,例如,我想使用一些其他的东西来组合mult5AfterAdd10,我真的会遇到麻烦。

所以由于我们不能将这个两个函数对接将会出现函数组合的作用受限。这太糟糕了因为函数组合是如此强大。

我们怎么解决这个问题?我们需要什么来消除这个问题?

然而,如果我们用某种方法提前仅给add函数一个参数,稍后mult5After10被调用时它(add)能获取到第二个参数,这才是真正妙的地方。

事实证明有一种方法,它就是柯里化(Currying)

本文译自 So You Want to be a Functional Programmer (Part 3)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容