fp编程案例_其他任何功能性编程(FP)…

fp编程案例

Don't worry, this is not YAMA (yet another monad article)! Instead, I want to talk about a library I've recently released that offers a helpful twist on typical functional programming ("FP") operations (like map(..), compose(..), etc).

别担心,这不是YAMA(还有另一篇monad文章)! 相反,我想谈谈我最近发布的库,该库为典型的函数式编程(“ FP”)操作(例如map(..)compose(..)等)提供了有益的帮助。

Before we jump in: if you're like me and have tried to understand FP (and how to apply it to JavaScript), only to be frustrated and intimated by crazy terminology like "functors" or fancy notation like L ::= x | (L L) | (λx.L), you might want to check out my latest book, Functional-Light JS (which you can read for free online!).

在我们开始之前:如果您像我一样并且试图理解FP(以及如何将其应用于JavaScript),那么只会被疯狂的术语(例如“ functors”)或像L ::= x | (LL) | (λx.L)这样的奇特符号所挫败和暗示L ::= x | (LL) | (λx.L) L ::= x | (LL) | (λx.L) L ::= x | (LL) | (λx.L) ,您可能想看看我的最新书《 Functional-Light JS》 (您可以在线免费阅读!)。

My book has a very different take; it approaches FP informally, from the ground-up, without being as heavy on terminology, and relies on almost no notation. The goal is to pragmatically explain the important fundamental concepts in ways you can actually use in your programs.

我的书截然不同。 它完全从头开始以非正式的方式对待FP,而没有那么繁琐的术语,并且几乎不依赖任何符号。 目的是用可以在程序中实际使用的方式,务实地解释重要的基本概念。

Note: From here on I'm going to expect you're familiar with ES6 features like ... spread and destructuring. Still fuzzy on those? No worries, I wrote a book on that, too! Check out You Don't Know JS: ES6 & Beyond, especially Chapter 2.

注意:从这里开始,我希望您熟悉ES6功能,例如...传播和解构。 还在模糊那些? 不用担心,我也为此写了一本书! 查看“ 您不知道JS:ES6及更高版本” ,尤其是第2章

问题 (The Problem)

There's already plenty of great FP libraries in JS, so why did I have the idea to build a new one!? Let me explain my motivations. Bear with me, because I want you to fully understand them to get why we need YAFPL. :)

JS中已经有很多很棒的FP库 ,所以为什么我有想法构建一个新的FP库呢? 让我解释一下我的动机。 忍受我,因为我希望您完全理解它们,以了解为什么我们需要YAFPL。 :)

Let's start first by looking at some code which illustrates one of my many frustrations as I've been learning and trying to work more with FP in my JavaScript. I'm going to use Ramda for this comparison, but any ol' regular FP-JS library will do:

首先,我们来看一些代码,这些代码说明了我一直在学习中的许多挫败之一,并尝试在JavaScript中使用FP。 我将使用Ramda进行比较,但是任何普通的FP-JS库都可以:


function lowercase(v) { return v.toLowerCase(); }
function uppercase(v) { return v.toUpperCase(); }

var words = ["Now","Is","The","Time"];
var moreWords = ["The","Quick","Brown","Fox"];

var f = R.map( uppercase );
f( words );                        // ["NOW","IS","THE","TIME"]
f( moreWords );                    // ["THE","QUICK","BROWN","FOX"]

As with all methods in Ramda, R.map(..) is curried, which means that even though it expects 2 arguments, we can call it with just uppercase, making a more specialized f(..) function that's then waiting for an array to map over. That lets us then call f(..) with different arrays, uppercasing each value in them, respectively.

R.map(..)所有方法一样, R.map(..)也经过咖喱处理,这意味着即使它期望有2个参数,我们也可以仅使用uppercase来调用它,从而制作一个更加专业的f(..)函数,然后等待要映射的数组。 然后,我们可以使用不同的数组调用f(..) ,分别将它们中的每个值大写。

What you may not realize is that inherently, the order of these arguments matters. R.map(..) expects the mapper function first and then the array. In this case, that's convenient for us because we want to specialize it in that sequence (mapper function first, array(s) later).

您可能没有意识到的是,这些参数的顺序本质上很重要。 R.map(..)需要映射器函数,然后是数组。 在这种情况下,这对我们来说很方便,因为我们要按该顺序对其进行专门化处理(首先是映射器函数,然后是数组)。

But what if we need to specialize in a different sequence (array first, mapper function later). This is possible, but takes a little extra work:

但是,如果我们需要专门处理其他序列(首先是数组,然后是mapper函数),该怎么办? 这是可能的,但需要做一些额外的工作:


var p = R.flip( R.map )( words );

p( lowercase );                 // ["now","is","the","time"]
p( uppercase );                 // ["NOW","IS","THE","TIME"]

We want to specify words first, making a p(..) that later takes a mapper function. Our specialization is with the second argument instead of the first.

我们要先指定words ,然后创建一个p(..) ,然后再使用一个映射器函数。 我们的专业是第二个参数,而不是第一个。

To accomplish this, we have to R.flip(..) the R.map(..) function. flip(..) makes a function wrapper that swaps the first two arguments when passing to the underlying function. By flipping the argument order of R.map(..), it now expects the array first, and the mapper function second.

为此,我们必须R.flip(..) R.map(..)函数。 flip(..)制作了一个函数包装器,该函数包装器在传递给基础函数时交换前两个参数。 现在,通过翻转R.map(..)的参数顺序,它首先需要数组,然后是映射器函数。

In other words, to work with standard FP methods across any of the FP libraries, you have to remember their argument order -- keep those docs handy! -- and if it happens to be in an inconvenient order, you're stuck doing this juggling. On more than one occasion, I've had to flip a method, pass in an argument, flip it again to pass in another argument, etc. All that juggling can quickly get out of hand!

换句话说,要在所有FP库中使用标准FP方法,您必须记住它们的参数顺序-随时随地使用这些文档! -如果碰巧不方便,则您将无法进行这种杂耍。 在多个情况下,我不得不翻转方法,传递参数,再次翻转以传递另一个参数,等等。所有这些杂耍都会很快失去控制!

Another frustration that arises from positional arguments is when you need to skip one (probably because it has a default you want to fall back on). For this example, I'm going to use lodash/fp:

位置参数引起的另一个挫败是您需要跳过一个参数时(可能是因为它具有默认值,您希望重新使用它)。 对于此示例,我将使用lodash/fp


function concatStr(s1,s2) { return s1 + s2; }

var words = ["Now","Is","The","Time"];

_.reduce( concatStr, _, words );
// NowIsTheTime

_.reduce( concatStr, "Str: ", words );
// Str: NowIsTheTime

The _.reduce(..) function expects arguments in this order: reducerFunction, initialValue, arr. The common understanding of reduce(..) in JS is that if you don't want to provide an initialValue, it doesn't just default to some magic empty value, but rather changes the behavior of the operation itself. Basically, it starts the reduction with the second element in the array, using the first element as the initialValue; this results in overall one less call to the reducer function (concatStr(..)).

_.reduce(..)函数按以下顺序期望参数: reducerFunctioninitialValuearr 。 JS中对reduce(..)的普遍理解是,如果您不想提供initialValue ,则它不仅会默认为一些不可思议的空值,而且会更改操作本身的行为。 基本上,它从数组中的第二个元素开始缩减,使用第一个元素作为initialValue ; 这样就减少了对reducer函数( concatStr(..) )的调用。

Unfortunately, JS doesn't let us just omit an argument in a call list, like _.reduce( concatStr,, words ). That would be cool, but no such luck. Instead, awkwardly, we have to pass a placeholder. Lodash lets us use _ as the placeholder by default, but in general, you typically have to use undefined.

不幸的是,JS不允许我们仅忽略呼叫列表中的参数,例如_.reduce( concatStr,, words ) 。 那将很酷,但是没有这种运气。 相反,尴尬的是,我们必须通过一个占位符。 Lodash让我们默认使用_作为占位符,但是通常,您通常必须使用undefined

Tip: There is a way to use a syntactic trick to avoid needing the placeholder in a normal JS function call: foobar( ...[1,2,,4] ). What we do is use an array literal, which does allow "ellision" (skipping a value), and then we spread it out using the ES6+ ... spread operator. foobar(..) here would receive arguments 1, 2, undefined, and 4 its first four parameter positions. I'm not sure if that hoop jumping is any better (and it may have some perf downsides!).

提示:使用句法特技以避免需要在正常JS函数调用占位符的方式: foobar( ...[1,2,,4] ) 我们要做的是使用数组文字,它确实允许“省略号”(跳过一个值),然后我们使用ES6 + ... spread运算符将其展开。 foobar(..)这里将接收的参数12undefined4其前四个参数的位置。 我不确定箍跳是否会更好(并且可能会有一些缺点)。

In any case, juggling argument order and jumping through hoops to skip arguments at the call site is a common frustration in JS. It just happens to be a rather acute pain in FP as you end up needing to use those API methods in different ways more often than with just normal application functions.

在任何情况下,在JS中,杂乱无章的参数顺序和跳过箍以跳过调用站点上的参数都是常见的问题。 在FP中,这恰好是一个非常严重的痛苦,因为您最终需要以多种方式使用这些API方法,而不仅仅是正常的应用程序功能。

解决方案:命名参数 (The Solution: Named Arguments)

Some languages have a syntax for naming arguments at the call site (not just naming parameters in the function declaration). For example, in Objective-C:

某些语言具有在调用站点上命名参数的语法 (而不仅仅是在函数声明中命名参数)。 例如,在Objective-C中:

[window addNewControlWithTitle:@"Title"
                     xPosition:20
                     yPosition:50
                         width:100
                        height:50
                    drawingNow:YES];

Here, you're calling the addNewControlWithTitle(..) function, and telling the system which parameter each value should be applied to, regardless of what order they may be listed in that function's declaration.

在这里,您要调用addNewControlWithTitle(..)函数,并告诉系统每个值应应用于哪个参数,而不管它们在该函数的声明中列出的顺序如何。

The benefit of named arguments is that you control at the call site which order you want to list arguments, and you also can just not list one if you don't want to pass a value for it. The tradeoff is that you have to remember what the parameters are called. Typically, languages and packages will adopt standardized naming conventions to help the parameter names be more intuitive and memorable.

命名参数的好处是, 您可以在呼叫站点控制要列出参数的顺序,如果不想传递值,也可以只列出一个。 折衷方案是您必须记住参数的名称 。 通常,语言和软件包将采用标准化的命名约定,以帮助参数名称更加直观和易于记忆。

Let me just say, this is not an either/or situation in my mind, in terms of code readability. There are times the positional arguments are more preferable, and clearly times when named arguments are more preferable. Ideally, a language would let you pick at the call site as you desire.

我只能说,就代码的可读性而言,这在我看来不是一种或非两种情况。 有时位置参数更可取,而命名参数显然更可取。 理想情况下,一种语言可以让您根据需要在呼叫站点进行选择。

Unfortunately, JS does not have named arguments. However, we do have a pattern that gives us pretty much all the benefits of named arguments. For example:

不幸的是,JS没有命名参数。 但是,我们确实有一种模式,可以给我们带来命名参数几乎所有的好处。 例如:


function foo(x,y = 2,z) {
    console.log( x, y, z );
}

function bar({ x, y = 2, z }) {        // <--- parameter object destructuring
    console.log( x, y, z );
}

foo( 1, undefined, 3 );                // 1 2 3
bar( {z:3, x:1} );                     // 1 2 3

Note: Typically you will want a bar(..) style function declaration to look like: function bar({ x, y = 2, z} = {}) { .. }. That = {} parameter default means the bar(..) function degrades gracefully if called without an object at all.

注意:通常,您将希望bar(..)样式的函数声明看起来像: function bar({ x, y = 2, z} = {}) { .. } 。 该= {}参数默认值表示bar(..)函数在根本没有对象的情况下调用时会正常降级。

With foo(..) we're using traditional positional arguments style, including the middle one (y) having a default. With bar(..) however, we're using the JS named-arguments idiom. First, we use parameter object destructuring in the parameter list. That essentially means we're declaring that we expect bar(..) to always be called with a single object as its argument. That object's properties are then destructured to be interpreted as the function's actual individual arguments, x, y, and z; again, y also has a default.

使用foo(..)我们使用的是传统的位置参数样式,包括中间的( y )具有默认值。 对于bar(..) ,我们使用的是JS named-arguments惯用语。 首先,我们在参数列表中使用参数对象分解。 从本质上讲,这意味着我们要声明希望bar(..)始终以单个对象作为其参数来调用。 然后将该对象的属性进行解构,以解释为函数的实际单个参数xyz ; 同样, y也有默认值。

The call site for foo(..) and bar(..) differ, too. For bar(..), we pass in an object with properties instead of individual values with an undefined as positional placeholder. The object-argument can list properties (named arguments) in any order, and omit any that it doesn't want to specify. Nice!

foo(..)bar(..)的调用站点也不同。 对于bar(..) ,我们传入一个具有属性的对象,而不是带有undefined为位置占位符的单个值。 对象参数可以以任何顺序列出属性(命名参数),并且可以省略它不想指定的任何内容。 真好!

适应 (Adaptation)

My personal rule of thumb is that I now prefer to define any function that takes 3 or more arguments (especially if one or more have defaults!) with the named-arguments style. But that's only helpful when I'm in control of the function declaration and can make that decision.

我个人的经验法则是,我现在更喜欢使用named-arguments样式定义任何带有3个或更多参数(特别是如果一个或多个具有默认值!)的函数。 但这仅在我控制函数声明并可以做出决定时有用。

What if I have a function like R.map(..) (or any other normal function in the application!) but I want to use named arguments at the call site?

如果我有一个类似R.map(..)的函数(或应用程序中的任何其他普通函数!),但我想在调用站点上使用命名参数,该怎么办?

To do so, we need to adapt a positional-arguments style function to be named-arguments style. Let's imagine such a helper for that; we'll call it apply(..):

为此,我们需要将位置参数样式函数改编为命名参数样式。 让我们想象一下这样的帮助者; 我们称它为apply(..)


function apply(fn,props) {
    return function applied(argsObj) {
        // map properties from `argsObj` to an array,
        // in the order of property names in `props`
        var args = [], i = 0;

        for (let prop of props) {
            args[i++] = argsObj[prop];
        }

        return fn( ...args );
    };
}

Since objects are fundamentally unordered, we pass a props array which lists the property names in the order we want them to map to the positional arguments of the underlying function.

由于对象基本上是无序的,因此我们传递了一个props数组,该数组以希望它们映射到基础函数的位置参数的顺序列出属性名称。

Let's use this utility now:

现在使用此实用程序:


var map = apply( R.map, ["fn","arr"] );

map( {arr: words, fn: lowercase} );            // ["now","is","the","time"]

OK, sorta cool, huh?

好吧,有点酷吧?

Unfortunately, the resulting map(..) is no longer usefully curried, so we can't really take advantage of this capability in any interesting way. Wouldn't it really be cool if we could do:

不幸的是,生成的map(..)不再有用,因此我们无法真正以任何有趣的方式利用此功能。 如果可以,那真的不是很酷吗:


var map = someSuperCoolAdapter( R.map, ["fn","arr"] );

var f = map( {fn: uppercase} );
f( {arr: words} );                            // ["NOW","IS","THE","TIME"]
f( {arr: moreWords} );                        // ["THE","QUICK","BROWN","FOX"]

var p = map( {arr: words} );
p( {fn: lowercase} );                         // ["now","is","the","time"]
p( {fn: uppercase} );                         // ["NOW","IS","THE","TIME"]

To do that, we'd probably need an apply(..) that was smart enough to automatically curry across multiple named arguments calls. I won't show how we'd do that, for brevity sake. But it's an interesting exercise for the reader. Another wrinkle: is there any way this adapter could figure out what property names to use by default? It is possible, if you parse the function definition (string regex parsing!). Again, I'll leave that for the reader to explore!

为此,我们可能需要一个足够聪明的apply(..)来自动咖喱多个命名参数调用。 为简洁起见,我不会显示我们将如何做。 但这对读者来说是一个有趣的练习。 另一个难题:该适配器有什么办法可以找出默认情况下要使用的属性名称? 如果您解析函数定义(字符串正则表达式解析!),则是可能的。 再次,我将其留给读者探索!

What about adapting the other direction? Say we have a named-arguments style function, but we just want to use it as a normal positional-arguments style function. We need a companion utility that does the inverse of apply(..); we'll call this one unapply(..):

如何适应另一个方向呢? 假设我们有一个命名参数样式函数,但我们只想将其用作常规的位置参数样式函数。 我们需要一个与apply(..)相反的伴随实用程序; 我们将其称为unapply(..)


function unapply(fn,props) {
    return function unapplied(...args) {
        // map `args` values to an object,
        // with property names from `props`
        var argsObj = {}, i = 0;

        for (let arg of args) {
            argsObj[ props[i++] ] = arg;
        }

        return fn( argsObj );
    };
}

And using it:

并使用它:


function foo({ x, y, z } = {}) {
    console.log( x, y, z );
}

var f = unapply( foo, ["x","y","z"] );

f( 1, 2, 3 );            // 1 2 3

Same problem here with currying. But at least we can now envision how armed with these two utilities, we can interoperate with positional-arguments style and named-arguments style functions, as we see fit!

currying同样的问题。 但是至少现在我们可以设想如何使用这两个实用程序武装,我们可以与位置自变量样式和命名自变量样式函数互操作,只要我们认为合适!

Reminder: all of this is entirely separate from whether we're dealing with an FP library or not. These concepts apply (pun intended) with any of your functions in your application. You can now freely define functions with either style as appropriate, and choose at the call site how you want to interface with a function. That's very powerful!

提醒:所有这些都与我们是否要处理FP库完全分开。 这些概念适用于您的应用程序中的任何功能。 现在,您可以根据需要使用两种样式自由定义函数,并在调用站点选择如何与函数接口。 真厉害!

FP库已经存在? (FP Library Already?)

Good grief, that was a really long preamble to ostensibly the main topic of this article, which is supposed to introduce a new FP library I've released. At least you understand why I wrote it. So now let me get to it!

悲哀的是,表面上这是本文主题的很长的序言,应该介绍我发布的新FP库。 至少你理解我为什么写它。 所以现在让我开始吧!

When conceiving of apply(..) / unapply(..) and playing around with them, I had this thought: what if I had a whole FP library where all the methods were already in named-arguments style? Of course, that library can also provide the apply(..) / unapply(..) helpers to make interop easier. And, for convenience, shouldn't that library also just export all the same methods (in a separate namespace) using the standard positional-arguments style? Ultimate choice in one FP lib, right!?

在构思apply(..) / unapply(..)并与它们一起玩耍时,我想到了:如果我拥有一个完整的FP库,其中所有方法都已经具有命名参数样式,那该怎么办? 当然,该库还可以提供apply(..) / unapply(..)帮助器,以简化互操作。 并且,为方便起见,该库是否还应该仅使用标准的位置参数样式导出所有相同的方法(在单独的命名空间中)? 在一个FP库中的最终选择,对吧?

That's what FPO (pronounced "eff-poh") is all about. FPO is a JS library for FP, but its core methods are all defined in the named-arguments style. As is common with FP libraries, all the methods are also curried, so you can provide arguments in whatever order and sequence you need! And FPO.std.* has all the positional-arguments style methods if you want them.

这就是FPO (发音为“ eff-poh”)的全部含义。 FPO是FP的JS库,但其核心方法均以named-arguments样式定义。 与FP库一样,所有方法也都经过咖喱处理,因此您可以按需要的顺序和顺序提供参数! 如果需要, FPO.std.*具有所有位置参数样式方法。

Want to jump straight to the docs?

想直接跳到文档吗?

  • Core API -- named-arguments style methods (FPO.map(..), etc)

    核心API-命名参数样式方法( FPO.map(..)等)

  • Standard API -- standard positional-arguments style methods (FPO.std.map(..), etc). These mostly work like their Ramda counterparts.

    标准API-标准位置参数样式方法( FPO.std.map(..)等)。 这些大多数都像Ramda的同行一样工作。

快速范例 (Quick Examples)


// Note: these functions now expect named-arguments style calls
function lowercase({ v } = {}) { return v.toLowerCase(); }
function uppercase({ v } = {}) { return v.toUpperCase(); }

var f = FPO.map( {fn: uppercase} );
f( {arr: words} );                            // ["NOW","IS","THE","TIME"]
f( {arr: moreWords} );                        // ["THE","QUICK","BROWN","FOX"]

var p = FPO.map( {arr: words} );
p( {fn: lowercase} );                         // ["now","is","the","time"]
p( {fn: uppercase} );                         // ["NOW","IS","THE","TIME"]

FPO.map(..) is named-arguments style, and already curried. Very easy to use however you want!

FPO.map(..)是命名参数样式,已经进行了咖喱处理。 非常容易使用,但是您想要!

As you'll notice, it expects its mapper function to also follow named-arguments style. If you instead want to pass a standard-style mapper function, just apply(..) it first:

您会注意到,它希望其映射器功能也遵循命名参数样式。 相反,如果要传递标准样式的映射器函数,只需先apply(..)


function firstChar(v) { return v[0]; }

var f = FPO.apply( {fn: firstChar} );          // <-- auto detects `props`!
FPO.map( {fn: f, arr: words} );                // ["N","I","T","T"]

Applying and currying are easy to mix together in your own code, too:

应用程序和currying也很容易在您自己的代码中混合在一起:


function foo(x,y,z) {
    console.log( x, y, z );
}

var f = FPO.apply( {fn: foo} );
var g = FPO.curry( {fn: f, n: 3} );

g( {y: 2} )( {x: 1} )( {z: 3} );               // curried named-arguments!
// 1 2 3

Unapplying works similarly:

取消应用的方法类似:


function foo({x, y = 2, z} = {}) {
    console.log( x, y, z );
}

var f = FPO.unapply( {fn: foo, props: ["x","y","z"]} );

f( 1, undefined, 3 );
// 1 2 3

But don't forget easy skipping of named arguments for defaults:

但是不要忘记轻松跳过默认值的命名参数:


function foo(x,y = 2,z) {
    console.log( x, y, z );
}

var g = FPO.curry( {
    fn: FPO.apply( {fn: foo} ),
    n: 2    // use `2` here for currying-count to allow skipping
} );

g( {z: 3} )( {x: 1} );
// 1 2 3

Composition of named-arguments style functions works, too:

命名参数样式函数的组成也可以:


function plus2({ v } = {}) { return v + 2; }
function triple({ v } = {}) { return v * 3; }
function decrement({ v } = {}) { return v - 1; }

FPO.map( {
    arr: [1,2,3,4,5],
    fn: FPO.compose( {fns: [
        decrement,
        triple,
        plus2
    ]} )
} );
// [8,11,14,17,20]

FPO.map( {
    arr: [1,2,3,4,5],
    fn: FPO.pipe( {fns: [
        plus2,
        triple,
        decrement
    ]} )
} );
// [8,11,14,17,20]

Lastly, the standard positional-argument style methods are still available if you want them:

最后,如果需要,标准位置参数样式方法仍然可用:


function concatStr(s1,s2) { return s1 + s2; }

FPO.std.reduce( concatStr, undefined, words );
// NowIsTheTime

Note: BTW, if you don't like typing FPO. or FPO.std. in front of all your methods, just alias those objects to whatever you prefer, like var F = FPO, S = FPO.std;. Eventually, FPO will even support ES6 modules style imports where you'll be able to import only the methods you want, into your own lexical scope!

注意:顺便说一句,如果您不喜欢键入FPO.FPO.std. 在所有方法之前,只需将那些对象别名var F = FPO, S = FPO.std;您喜欢的对象,例如var F = FPO, S = FPO.std; 。 最终,FPO甚至将支持ES6模块样式导入,在其中您将只能将所需的方法导入自己的词法范围!

That's a quick overview of what you can do with FPO. Go check out the README overview and API Docs for further information!

这是您可以使用FPO进行的快速概述。 请查看自述 文件概述和API文档以获取更多信息!

参数命名约定 (Parameter Naming Conventions)

FPO has a fairly straightforward approach for parameter naming conventions, which should be reasonable to intuit and learn. A glimpse:

FPO对于参数命名约定有一种相当简单的方法,这对于理解和学习应该是合理的。 惊鸿一瞥:

  • When a method expects a function, the named argument is fn.

    当方法需要函数时,命名参数为fn

  • When a method expects a number, the named argument is n.

    当方法需要数字时,命名参数为n

  • When a method expects a value, the named argument is v.

    当方法需要一个值时,命名参数为v

  • ...

    ...

The full list of rules are listed here.

规则的完整列表在此处列出。

Wrap(..)向上 (Wrap(..)ing Up)

OK, that's FPO.

好的,那是FPO

I'm not trying to compete with libraries like Ramda or lodash/fp. They're great. I just wanted to provide some additional flexibility. And in my FP coding so far, I'm finding the tradeoffs and flexibility to be a nice improvement!

我不是要与Ramda或lodash / fp之类的库竞争。 他们真棒。 我只是想提供一些额外的灵活性。 到目前为止,在我的FP编码中,我发现权衡和灵活性是一个不错的改进!

I hope you find FPO useful! Let me know in the comments, or chime in on the repo issues if you have suggestions or questions.

希望您觉得FPO有用! 在评论中让我知道,如果您有建议或问题,请就回购问题进行咨询。

翻译自: https://davidwalsh.name/functional-programming

fp编程案例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值