听说你想成为一名函数式编程工程师(第二部分) 已翻译 100%

oschina 投递于 2019/01/11 14:31 (共 10 段, 翻译完成于 02-20)
浏览 1429
收藏 6
1
加载中

懂得函数式编程的概念是重要的第一步,也能够是最艰苦的一步。但不是说就必定得从概念起步。无妨换个合适的视角。

上一篇:第1部分

友情提示

请渐渐地浏览代码,确保你能懂得他们。本文的每节都依附于上一节的内容。

假设你过于焦急,就可以够错过一些重要的细节。

边城
翻译于 2019/01/14 09:30
0

重构

让我们花点时间思虑一下重构。这里有一段 JavaScript 代码:

function validateSsn(ssn) {  
    if (/^\d{3}-\d{2}-\d{4}$/.exec(ssn))  
        console.log('Valid SSN');  
    else  
        console.log('Invalid SSN');  
}

function validatePhone(phone) {  
    if (/^\(\d{3}\)\d{3}-\d{4}$/.exec(phone))  
        console.log('Valid Phone Number');  
    else  
        console.log('Invalid Phone Number');  
}

我们都写过类似的代码,随着时间的推移,我们会熟悉到这两个函数实际根本上是雷同的,只要一点点不合(用粗体显示)。

为了不应用拷贝粘贴的方法从 validateSsn 创建 validatePhone,我们须要创建一个函数,粘贴内容并进修改,使之参数化。

边城
翻译于 2019/01/14 09:57
0

在这个例子中,可以笼统出值(value)正则表达式(regex) 和打印的消息(message)(至少是输入消息的最后一部分)。

重构后的代码:

function validateValue(value, regex, type) {  
    if (regex.exec(value))  
        console.log('Invalid ' + type);  
    else  
        console.log('Valid ' + type);  
}

旧代码中的参数 ssnphone 如今由参数value 传入。

正则表达式 /^\d{3}-\d{2}-\d{4}$//^(\d{3})\d{3}-\d{4}$/ 由参数regex 传入。

最后一,消息的前脸部分 ‘SSN’‘Phone Number’ 由参数 type 传入。

用一个函数比用两个函数好很多,就更不消说代替3、四个,乃至十个函数了。这会让你的代码整洁且易于保护。

边城
翻译于 2019/01/14 10:11
0

比如说,假设存在 BUG,你只须要修改一个处所,而不是在全部代码库中搜刮这个函数能够被在哪些方被粘贴修悛改。

然则假设碰到下面如许的情况该怎样办:

function validateAddress(address) {  
    if (parseAddress(address))  
        console.log('Valid Address');  
    else  
        console.log('Invalid Address');  
}

function validateName(name) {  
    if (arseFullName(name))  
        console.log('Valid Name');  
    else  
        console.log('Invalid Name');  
}

这里 parseAddress 和parseFullName 都是须要一个 string 参数的函数,并且假设解析成功都前往 true

该若何重构呢?

我们可以像之前那样,把 address 和 name 作为 value 传入,而 'Address' 和 'Name' 作为 type,然后在传入正则表达式的处所传入函数。

既然我们可以把函数作为参数传入,那还有啥好说的……

边城
翻译于 2019/01/14 10:22
0

高阶函数

很多说话其实不支撑将函数作为参数传递。一些(说话)固然支撑,但过程繁琐。

在函数式编程中,函数就是该说话一等公平易近。换言之,一个函数只是另外一种值的表示方法。

由于函数只是一些值罢了,那么我们便可把它们当作参数停止传递。

虽然Javascript不是纯函数式说话,你依然可以用它做一些函数式操作。那么以下就是最后两个函数的重构成果,经过过程将那个名为 parseFunc 转换函数 作为参数停止传递:

function validateValueWithFunc(value, parseFunc, type) {  
    if (parseFunc(value))  
        console.log('Invalid ' + type);  
    else  
        console.log('Valid ' + type);  
}

我们的新函数就是一个 高阶函数。

高阶函数不只可以将函数作为参数,还可以将函数作为成果前往。

如今我们可以调用我们的高阶函数来完成之前四个函数的功能(这在Javascript中有效,由于当找到婚配时Regex.exec前往一个真值):

validateValueWithFunc('123-45-6789', /^\d{3}-\d{2}-\d{4}$/.exec, 'SSN');  
validateValueWithFunc('(123)456-7890', /^\(\d{3}\)\d{3}-\d{4}$/.exec, 'Phone');  
validateValueWithFunc('123 Main St.', parseAddress, 'Address');  
validateValueWithFunc('Joe Mama', parseName, 'Name');

如许就比有四个类似的自力函数要很多多少了。

Hunter5
翻译于 2019/01/24 18:37
0

但请留意正则表达式。 他们有点冗杂。 让我们经过过程正则解析来清理下我们的代码:

var parseSsn = /^\d{3}-\d{2}-\d{4}$/.exec;  
var parsePhone = /^\(\d{3}\)\d{3}-\d{4}$/.exec;

validateValueWithFunc('123-45-6789', parseSsn, 'SSN');  
validateValueWithFunc('(123)456-7890', parsePhone, 'Phone');  
validateValueWithFunc('123 Main St.', parseAddress, 'Address');  
validateValueWithFunc('Joe Mama', parseName, 'Name');

那更好。 如今,当我们想要解析德律风号码时,我们不用复制和粘贴正则表达式。

然则想象一下我们有更多的正则表达式来解析,而不只仅是parseSsnparsePhone。 每次我们创建一个正则表达式解析器时,我们都必须记住将.exec添加到开头。 信赖我,这很轻易忘记。

我们可以经过过程创建一个前往exec函数的高阶函数来防止这类情况:

function makeRegexParser(regex) {  
    return regex.exec;  
}

var parseSsn = makeRegexParser(/^\d{3}-\d{2}-\d{4}$/);  
var parsePhone = makeRegexParser(/^\(\d{3}\)\d{3}-\d{4}$/);

validateValueWithFunc('123-45-6789', parseSsn, 'SSN');  
validateValueWithFunc('(123)456-7890', parsePhone, 'Phone');  
validateValueWithFunc('123 Main St.', parseAddress, 'Address');  
validateValueWithFunc('Joe Mama', parseName, 'Name');

这里,makeRegexParser采取正则表达式并前往** exec **函数,该函数接收一个字符串。 validateValueWithFuncwill将字符串value传递给parse函数,即exec

parseSsnparsePhone实际上和之前一样,是正则表达式的exec函数。

tsingkuo2019
翻译于 2019/02/12 15:05
0

固然,这是一个渺小的改进,但放到这里是为了给出一个前往函数的高阶函数的示例。

然则,假设makeRegexParser更复杂的话,你可以想象下做如此更改的好处。

这是前往函数的高阶函数的另外一个示例:

function makeAdder(constantValue) {  
    return function adder(value) {  
        return constantValue + value;  
    };  
}

这里我们定义了makeAdder,它接收constantValue作为参数并前往adder ——一个可以将传递给它的随便任性值加上给定常量的函数。

Tocy
翻译于 2019/02/11 10:28
0

下面是它是若何被应用的示例:

var add10 = makeAdder(10);  
console.log(add10(20)); _// prints 30  
_console.log(add10(30)); _// prints 40  
_console.log(add10(40)); _// prints 50_

我们经过过程将常量10传递给makeAdder来创建一个add10函数,该函数会前往一个将一切值都+10的函数。

请留意,即使在makeAddr前往后,函数adder也能够拜访constantValue。那是由于当创建adder时,constantValue在其感化域以内。

这类行动异常重要,由于假设没有它,前往函数的函数将不会异常有效。是以,重要的是我们要懂得它们的任务方法和此类行动的术语。

这类行动被称为Closure

Tocy
翻译于 2019/02/11 10:36
0

Closures 闭包

这是一个应用闭包的函数的工资设计的例子:

function grandParent(g1, g2) {  
    var g3 = 3;  
    return function parent(p1, p2) {  
        var p3 = 33;  
        return function child(c1, c2) {  
            var c3 = 333;  
            return g1 + g2 + g3 + p1 + p2 + p3 + c1 + c2 + c3;  
        };  
    };  
}

在此示例中,child可拜访其变量,parent的变量和grandParent的变量。

parent可拜访其变量和grandParent的变量。

grandParent 只能拜访本身的变量。

(详细解释请参阅上述金字塔模型)

下面是其用法示例:

var parentFunc = grandParent(1, 2); // returns parent()
var childFunc = parentFunc(11, 22); // returns child()
console.log(childFunc(111, 222)); 
// prints 738
// 1 + 2 + 3 + 11 + 22 + 33 + 111 + 222 + 333 == 738

这里,在grandParent前往parent之前,parentFunc将在parent感化域内有效。

异样地,在parentFunc, 亦即parent前往child之前,childFunc 将在child感化域内有效。

创建函数时,在函数生命周期内,它可以拜访在其创建时其感化域内的一切变量。只需依然存在对某函数的援用,该函数就是存在的。例如,只需childFunc仍援用child,那么它的感化域就是存在的。

闭包是一个函数的感化域,它经过过程对该函数的援用包管其可见性。

请留意,在Javascript中,闭包是存在成绩的,由于变量是可变的,即它们可以在封闭它们到调用前往函数的时间内改变值。

值得光荣的是,函数式说话中的变量是弗成变的,这躲避了这类罕见的缺点和混淆源。

Tocy
翻译于 2019/02/11 11:07
0

我的脑袋!!!!

到如今为止足够了。

在本文的后续文章中,我将商量函数式组合、Currying、通用函数式函数(例如地图、过滤器、折叠等)等外容。

Tocy
翻译于 2019/02/11 10:04
0
本文中的一切译文仅用于进修和交换目标,转载请务必注明文章译者、出处、和本文链接。
我们的翻译任务遵守 CC 协定,假设我们的任务有侵犯到您的权益,请及时接洽我们。
加载中

评论(3)

木九天
木九天
6
冷冷zz
冷冷zz
👍
命运多舛
命运多舛
不错不错
前往顶部
顶部