javascript函数式
by rajaraodv
通过rajaraodv
JavaScript中的函数式编程—结合实际示例(第2部分) (Functional Programming In JavaScript — With Practical Examples (Part 2))
In Part 1, we talked through: Functional Programming basics, Currying, Pure Functions, “Fantasy-land” specs, “Functors”, “Monads”, “Maybe Monads” and “Either Monads” via couple of examples.
在第1部分中 ,我们讨论了:函数式编程基础知识,Currying,纯函数,“幻想世界”规范, 通过几个示例可以看出“ Functors”,“ Monads”,“ Maybe Monads”和“ Either Monads”。
In this part, we’ll cover: Applicative, curryN function and “Validation Applicative”.
在这一部分中,我们将介绍:应用程序,curryN函数和“验证应用程序”。
Thanks to FP gurus Brian Lonsdorf, keithalexander and others for reviewing ??
感谢FP专家Brian Bons Lonsdorf , keithalexander和其他人对??
示例3 —将值分配给可能为空的对象 (Example 3— Assigning Values To Potentially Null Objects)
FP Concepts Used: “Applicative”
使用的FP概念:“适用”
Use Case: Let’s say we want to give discount to the user if the user is logged in and if we are running promotion (i.e. discount exists).
用例:假设我们要给用户折扣,如果用户已登录并且我们正在促销(即存在折扣)。
Let’s say we are using the applyDiscount method below. As you can imagine, applyDiscount might throw null errors if either the user (the left-hand side or the discount (the right-hand side) is null.
假设我们正在使用下面的applyDiscount方法。 可以想象,如果用户(左侧)或折扣(右侧)为null,则applyDiscount可能会引发null错误。
//Adds discount to the user object if BOTH user and discount exists.
//Throws null errors if either user or discount is nullconst applyDiscount = (user, discount) => { let userClone = clone(user);// use some lib to make a copy
userClone.discount = discount.code; return userClone;
}
//Adds discount to the user object if BOTH user and discount exists.
//Throws null errors if either user or discount is nullconst applyDiscount = (user, discount) => { let userClone = clone(user);// use some lib to make a copy
userClone.discount = discount.code; return userClone;
userClone.discount = discount.code; return userClone;
}
Let’s see how we can solve this using “applicative”.
让我们看看如何使用“应用程序”解决此问题。
Applicative:
适用范围:
Any Class that have a method “ap” and implements the Applicative spec is called an Applicative. Applicatives can be used in functions that are dealing with null values on both left-hand-side(user) and right-hand-side(discount) of the equation.
具有方法“ ap”并实现应用规范的任何类都称为应用。 可以在等式的左侧(用户)和右侧(折扣)上处理空值的函数中使用应用程序。
It turns out “Maybe” Monads (and every Monads) also implement “ap” spec and hence are also “Applicatives” and not just Monads. So we can use “Maybe” Monads to deal with null at function level.
事实证明,“也许” Monads(以及每个Monads)也执行“ ap”规范,因此也是“ Applicatives”,而不仅仅是Monads。 因此,我们可以在函数级别使用“也许”单声道来处理null。
Let’s see how we can solve make applyDiscount work using Maybe used as an “applicative”.
让我们看看如何使用Maybe用作“应用程序”来解决使applyDiscount工作的问题。
步骤1:在Maybe Monads中包装潜在的空值 (Step 1: wrap our potential null values in Maybe Monads)
const maybeUser = Maybe(user);
const maybeDiscount = Maybe(discount);
const maybeUser = Maybe(user);
const maybeDiscount = Maybe(discount);
第2步:重写函数并对其进行咖喱处理,以便我们一次可以传递一个参数。 (Step 2: Rewrite the function and curry it so we can pass one param at a time.)
//Rewrite the function and curry it so we can
//pass one param at a time
var applyDiscount = curry(function(user, discount) {
user.discount = discount.code;
return user;
});
//Rewrite the function and curry it so we can
//pass one param at a time
var applyDiscount = curry(function(user, discount) {
user.discount = discount.code;
return user;
});
步骤3:让我们通过“地图”将第一个参数(maybeUser)传递给applyDiscount。 (Step 3: let’s pass the first argument(maybeUser) to applyDiscount via “map”.)
//pass the first argument to applyDiscount via "map"
const maybeApplyDiscountFunc = maybeUser.map(applyDiscount);//Note, since applyDiscount is "curried", and "map" will only pass 1 parameter, the return result (maybeApplyDiscountFunc) will be a Maybe wrapped "applyDiscount" function that now has maybeUser(1st param) in it's closure.In other words, we now have a function wrapped in a Monad!
//pass the first argument to applyDiscount via "map"
const maybeApplyDiscountFunc = maybeUser.map(applyDiscount); //Note, since applyDiscount is "curried", and "map" will only pass 1 parameter, the return result ( maybeApplyDiscountFunc ) will be a Maybe wrapped "applyDiscount" function that now has maybeUser(1st param) in it's closure. In other words, we now have a function wrapped in a Monad!
const maybeApplyDiscountFunc = maybeUser.map(applyDiscount); //Note, since applyDiscount is "curried", and "map" will only pass 1 parameter, the return result ( maybeApplyDiscountFunc ) will be a Maybe wrapped "applyDiscount" function that now has maybeUser(1st param) in it's closure. In other words, we now have a function wrapped in a Monad!
步骤4:处理 maybeApplyDiscountFunc (Step 4: Deal With maybeApplyDiscountFunc)
At this stage maybeApplyDiscountFunc can be:1. If user actually exists, then maybeApplyDiscountFunc is a function wrapped inside a Maybe.2. If the user does not exist, then maybeApplyDiscountFunc will be “Nothing” (subclass of Maybe)
在这个阶段,applyDiscountFunc可以是: 1.如果用户确实存在,那么applyDiscountFunc是包装在Maybe中的函数。 2.如果用户不存在,则ApplyDiscountFunc可能为“ Nothing”(Maybe的子类)
If user doesn’t exist, then “Nothing” is returned and any further interaction with this are ignore completely. So if we pass 2nd argument, nothing happens. And also no Null errors are thrown.
如果用户不存在,则返回“ Nothing”,并且与之进行的任何进一步交互都将被完全忽略。 因此,如果我们通过第二个参数,则什么也不会发生。 而且也不会抛出Null错误。
But in the case where the user actually exists, we can try to pass the 2nd argument to maybeApplyDiscountFunc via “map” to execute the function like below:
但是在用户实际存在的情况下,我们可以尝试通过“ map”将第二个参数传递给maybeApplyDiscountFunc,以执行如下功能:
maybeDiscount.map(maybeApplyDiscountFunc)! // PROBLEM!
maybeDiscount.map(maybeApplyDiscountFunc)! // PROBLEM!
Uh oh! “map” doesn’t know how to run function(maybeApplyDiscountFunc) when the function itself is inside a MayBe!
哦! 当功能本身在MayBe内时, “地图”不知道如何运行功能(也许是ApplyDiscountFunc)!
That’s why we need a different interface to deal with this scenario. It turns out that’s “ap”!
这就是为什么我们需要一个不同的界面来处理这种情况的原因。 原来那是“ ap”!
Step5: Let’s recap “ap” function. “ap” method takes another Maybe monad and passes/applies the function it’s currently storing to that Maybe.
步骤5:让我们回顾一下“ ap”功能。 “ ap”方法采用另一个Maybe monad,并将当前存储的功能传递/应用到该Maybe。
So we can simply apply (“ap”) maybeApplyDiscountFunc to maybeDiscount instead of using “map” like below and it’ll work like a charm!
因此,我们可以简单地将(app)DistanceApplyDiscountFunc应用于(也许)而不是像下面那样使用“ map”,它将像魅力一样起作用!
maybeApplyDiscountFunc.ap(maybeDiscount)//Internally it is doing the following because applyDiscount is store in the this.val of maybeApplyDiscountFunc wrapper:
maybeDiscount.map(applyDiscount)//Now, if maybeDiscount actually has the discount, then the function is is run.If maybeDiscount is Null, then nothing happens.
maybeApplyDiscountFunc. ap (maybeDiscount)//Internally it is doing the following because applyDiscount is store in the this.val of maybeApplyDiscountFunc wrapper:
maybeApplyDiscountFunc. ap (maybeDiscount)//Internally it is doing the following because applyDiscount is store in the this.val of maybeApplyDiscountFunc wrapper:
maybeDiscount.map(applyDiscount)//Now, if maybeDiscount actually has the discount, then the function is is run.If maybeDiscount is Null, then nothing happens.
FYI: Apparently there is a change in the FL spec, The old version has (eg): `Just(f).ap(Just(x))` (where `f` is a function and `x` is a value) but the new version would have you write `Just(x).ap(Just(f))`But the implementations mostly haven’t changed yet. Thanks keithalexander
仅供参考:显然FL规范有所变化,旧版本有(例如):`Just(f).ap(Just(x))`(其中`f`是函数,`x`是值)但是新版本将要求您编写`Just(x).ap(Just(f))`,但实现方式大部分尚未改变。 谢谢keithalexander
To summarize, if you have a function that deals with multiple parameters that might all be null, you curry it first, then put it inside a Maybe. Further, also put all params in a Maybe and then use “ap” to run the function.
总而言之,如果您有一个处理多个可能都为空的参数的函数,则首先对其进行咖喱处理,然后将其放入Maybe中。 此外,还将所有参数放在Maybe中,然后使用“ ap”运行该函数。
咖喱功能 (curryN function)
We are familiar with “curry”. It simply converts a function that takes multiple arguments to take them one-by-one.
我们熟悉“咖喱”。 它只是简单地转换了一个函数,该函数需要多个参数才能将它们一对一地接受。
//Curry Example:
const add = (a, b) =>a+b;const curriedAdd = R.curry(add);const add10 = curriedAdd(10);//pass the 1st argument. Returns a function that takes 2nd (b) parameter.//run function by passing 2nd argument
add10(2) // -> 12 //internally runs "add" with 10 and 2.
//Curry Example:
const add = (a, b) =>a+b;const curriedAdd = R.curry(add);const add10 = curriedAdd(10);//pass the 1st argument. Returns a function that takes 2nd (b) parameter.//run function by passing 2nd argument
const add = (a, b) =>a+b;const curriedAdd = R.curry(add);const add10 = curriedAdd(10);//pass the 1st argument. Returns a function that takes 2nd (b) parameter.//run function by passing 2nd argument
add10(2) // -> 12 //internally runs "add" with 10 and 2.
const add = (a, b) =>a+b;const curriedAdd = R.curry(add);const add10 = curriedAdd(10);//pass the 1st argument. Returns a function that takes 2nd (b) parameter.//run function by passing 2nd argument
add10(2) // -> 12 //internally runs "add" with 10 and 2.
But instead of adding just two numbers, what if the add function can sum up all the numbers passed to it as an argument?
但是,如果不将两个数字相加,那么add函数可以将作为参数传递给它的所有数字相加呢?
const add = (...args) => R.sum(args); //sum all the numbers in args
const add = (...args) => R.sum(args); //sum all the numbers in args
We can still curry it by limiting number of args using curryN like below:
我们仍然可以通过使用curryN限制arg的数量来咖喱它,如下所示:
//curryN example
const add = (...args) => R.sum(args);//CurryN Example:
const add = (...args) => R.sum(args);const add3Numbers = R.curryN(3, add);
const add5Numbers = R.curryN(5, add);
const add10Numbers = R.curryN(10, add);add3Numbers(1,2,3) // 6
add3Numbers(1) // returns a function that takes 2 more params.
add3Numbers(1, 2) // returns a function that take 1 more param.
//curryN example
const add = (...args) => R.sum(args);//CurryN Example:
const add = (...args) => R.sum(args);const add3Numbers = R. curryN (3, add);
const add5Numbers = R. curryN (5, add);
const add10Numbers = R. curryN (10, add);add3Numbers(1,2,3) // 6
add3Numbers(1) // returns a function that takes 2 more params.
add3Numbers(1, 2) // returns a function that take 1 more param.
使用“ curryN”等待函数调用次数 (Using “curryN” to wait for number of function calls)
Let’s say we want to write a function that only logs if we call it 3 times (and ignore the 1st and 2nd call). Something like below:
假设我们要编写一个仅在调用3次后才记录日志的函数(而忽略第一次和第二次调用)。 如下所示:
//impure
let counter = 0;
const logAfter3Calls = () => {
if(++counter == 3)
console.log('called me 3 times');
}logAfter3Calls() // Nothing happens
logAfter3Calls() // Nothing happens
logAfter3Calls() // 'called me 3 times'
//impure
let counter = 0;
const logAfter3Calls = () => {
if(++counter == 3)
console.log('called me 3 times');
const logAfter3Calls = () => {
console.log('called me 3 times');
}logAfter3Calls() // Nothing happens
logAfter3Calls() // Nothing happens
logAfter3Calls() // 'called me 3 times'
We can simulate that using curryN like below.
我们可以使用curryN进行模拟,如下所示。
//Pure
const log = () => {
console.log('called me 3 times');
}const logAfter3Calls = R.curryN(3, log);//call
logAfter3Calls('')('')('')//'called me 3 times'//Note: We are passing '' to satisfy CurryN that we are passing some parameter.
//Pure
const log = () => {
console.log('called me 3 times');
} const logAfter3Calls = R.curryN(3, log); //call
} const logAfter3Calls = R.curryN(3, log); //call
logAfter3Calls('')('')('') //'called me 3 times'//Note: We are passing '' to satisfy CurryN that we are passing some parameter.
Note: We’ll be using this technique in the Applicative validation.
注意:我们将在Applicative验证中使用此技术。
示例4 —收集和显示多个错误 (Example 4— Collecting And Displaying Multiple Errors)
Topics covered: Validation (aka “Validation Functor”, “Validation Applicative”, “Validation Monad”).
涵盖的主题: 验证(又名“验证函子”,“验证适用性”,“验证单子”) 。
Validations are commonly referred as Validation Applicative because it is commonly used for validation using it’s “ap”(apply) function.
验证通常称为“ 验证适用性”,因为它通常使用其“ ap”(应用)功能进行验证。
Validations are similar to Either Monads and used to work with composing multiple error-throwing functions. But unlike with Either Monad, where we typically use its “chain” method to compose, in Validation Monads, we typically use “ap” method to compose. And unlike either’s “chain” method, where we only collect the 1st error, “ap” method, especially in Validation Monads allows us to collect all the errors in an Array.
验证与Either Monads相似,并且用于组合多个错误抛出函数。 但是与Either Monad不同的是,我们通常使用其“链”方法进行撰写,而在Validation Monad中,我们通常使用“ ap”方法进行撰写。 与任何一个“链”方法(我们只收集第一个错误)不同, “ ap”方法(尤其是在Validation Monad中)使我们可以将所有错误收集到Array中 。
They are typically used in form validation where we may want to show all the errors at the same time.
它们通常用于表单验证中,我们可能希望同时显示所有错误。
Use case: We have a sign up form that validates username, password and email using 3 functions(isUsernameValid, isPwdLengthCorrect and ieEmailValid. We need to show all 1, 2 or 3 errors if they all occur at the same time.
用例:我们有一个注册表单,使用3个函数(isUsernameValid,isPwdLengthCorrect和ieEmailValid)验证用户名,密码和电子邮件。如果它们同时发生,则需要显示所有1、2或3个错误。
OK, let’s see how to implement it using “Validation Applicative”.
好的,让我们看看如何使用“ Validation Applicative”来实现它。
We’ll use data.validation lib from folktalejs because ramda-fantasy doesn’t implement it yet.
因为ramda-fantasy尚未实现它,所以我们将使用民俗故事中的 data.validation库。
Similar to “Either” Monad, it has two constructors: Success and Failure. These are like subclasses that each implement Either’s specs.
与“任何一个” Monad相似,它具有两个构造函数: Success和Failure 。 这些就像每个实现Either规范的子类。
Step1: In order to use Validation, all we need to do is to wrap valid values and errors inside Success and Failure constructors (i.e. create instances of those classes).
步骤1:为了使用Validation,我们要做的就是将有效值和错误包装在Success和Failure构造函数中(即创建这些类的实例)。
const Validation = require('data.validation') //from folktalejs
const Success = Validation.Success
const Failure = Validation.Failure
const R = require('ramda');//Instead Of:
function isUsernameValid(a) {
return /^(0|[1-9][0-9]*)$/.test(a) ?
["Username can't be a number"] : a
}//Use:
function isUsernameValid(a) {
return /^(0|[1-9][0-9]*)$/.test(a) ?
Failure(["Username can't be a number"]) : Success(a)
}
const Validation = require('data.validation') //from folktalejs
const Success = Validation.Success
const Failure = Validation.Failure
const R = require('ramda'); //Instead Of:
const R = require('ramda'); //Instead Of:
function isUsernameValid(a) {
return /^(0|[1-9][0-9]*)$/.test(a) ?
["Username can't be a number"] : a
} //Use:
function isUsernameValid(a) {
return /^(0|[1-9][0-9]*)$/.test(a) ?
Failure (["Username can't be a number"]) : Success (a)
}
Repeat the process for ALL error throwing validation functions.
对所有错误抛出验证功能重复该过程。
Step 2: Create a dummy function to hold validation success.
步骤2:创建一个虚拟函数以保持验证成功。
const returnSuccess = () => 'success';//simply returns success
const returnSuccess = () => 'success';//simply returns success
Step 3: Use curryN to repeatedly apply “ap”
步骤3:使用curryN重复应用“ ap”
The problem with “ap” is that the left-hand side should be a functor (or a monad) containing function.
“ ap”的问题在于,左侧应该是包含函数的函子(或monad)。
For example, let’s say we want to repeatedly apply “ap” like below. It will only work if monad1 contains a function. And the result of monad1.ap(monad2) i.e. resultingMonad is also a monad with a function so that we can “ap” to monad3.
例如,假设我们要像下面一样重复应用“ ap”。 仅在monad1包含函数的情况下才有效。 和monad1.ap(monad2)的结果,即resultingMonad也有功能的单子,这样我们就可以“AP”,以monad3。
let finalResult = monad1.ap(monad2).ap(monad3)//Can be rewritten as:
let resultingMonad = monad1.ap(monad2)
let finalResult = resultingMonad.ap(monad3)//will only work if: monad1 has a function and monad1.ap(monad2) results in another monad (resultingMonad) with a function
let finalResult = monad1.ap(monad2).ap(monad3) //Can be rewritten as:
let resultingMonad = monad1.ap(monad2)
let finalResult = resultingMonad.ap(monad3) //will only work if: monad1 has a function and monad1.ap(monad2) results in another monad (resultingMonad) with a function
Generally speaking, we need 2 monads that has functions in order to apply “ap” twice.
一般而言,我们需要2个具有功能的单子,以便两次应用“ ap”。
In our case, we have 3 functions that we need to apply.
在我们的案例中,我们需要应用3个函数。
Let’s say we did something like below.
假设我们做了以下类似的事情。
Success(returnSuccess)
.ap(isUsernameValid(username)) //works
.ap(isPwdLengthCorrect(pwd))//wont work
.ap(ieEmailValid(email))//wont work
Success(returnSuccess)
.ap(isUsernameValid(username)) //works
.ap(isPwdLengthCorrect(pwd))//wont work
.ap(ieEmailValid(email))//wont work
The above won’t work because Success(returnSuccess).ap(isUsernameValid(username)) will result in a value. And we can no longer continue to do “ap” on 2nd and 3rd function.
上面的方法不起作用,因为Success(returnSuccess).ap(isUsernameValid(username))将产生一个值。 而且我们不能再继续对第二和第三功能执行“ ap”操作。
Enter curryN.
输入curryN。
We can use curryN to keep returning function until it is called “N” number of times.
我们可以使用curryN来保持返回功能,直到被称为“ N”次为止。
So we can simply do:
因此,我们可以简单地执行以下操作:
//3 coz we are calling "ap" 3 times.
let success = R.curryN(3, returnSuccess);
//3 coz we are calling "ap" 3 times.
let success = R.curryN(3, returnSuccess);
Now, the curried success keeps returning function 3 times.
现在, 成功的成功保持了3次返回功能。
function validateForm(username, pwd, email) {
//3 coz we are calling "ap" 3 times.
let success = R.curryN(3, returnSuccess); return Success(success)// default; used for 3 "ap"s
.ap(isUsernameValid(username))
.ap(isPwdLengthCorrect(pwd))
.ap(ieEmailValid(email))
}
function validateForm(username, pwd, email) {
//3 coz we are calling "ap" 3 times.
let success = R.curryN(3, returnSuccess); return Success(success)// default; used for 3 "ap"s
let success = R.curryN(3, returnSuccess); return Success(success)// default; used for 3 "ap"s
.ap(isUsernameValid(username))
let success = R.curryN(3, returnSuccess); return Success(success)// default; used for 3 "ap"s
.ap(isPwdLengthCorrect(pwd))
.ap(ieEmailValid(email))
}
Putting it all together:
放在一起:
If you liked the post by clicking on the ? it below and sharing it on Twitter! Thanks for reading! ??
如果您喜欢该帖子,请单击“?”。 并在Twitter上分享! 谢谢阅读! ??
我的其他帖子 (My Other Posts)
LATEST: The Inner workings of the Browser — for JavaScript & Web Developers Use code: INNER15 and get 50% off!
最新消息: 浏览器的内部工作原理-适用于JavaScript和Web开发人员 使用代码:INNER15可获得50%的折扣!
功能编程 (Functional Programming)
Functional Programming In JS — With Practical Examples (Part 1)
Functional Programming In JS — With Practical Examples (Part 2)
ES6 (ES6)
Web包装 (WebPack)
Webpack & Hot Module Replacement [HMR] (under-the-hood)
Webpack和热模块更换[HMR] (在内部)
Draft.js (Draft.js)
React And Redux: (React And Redux :)
A Guide For Building A React Redux CRUD App (3-page app)
构建React Redux CRUD应用程序指南 (3页应用程序)
销售队伍 (Salesforce)
javascript函数式