我在哪里可以找到用于解释函数式编程的符号的解释/摘要,特别是 Ramda.js? [英] Where can I find an explanation/summary of symbols used to explain functional programming, specifically Ramda.js?

查看:21
本文介绍了我在哪里可以找到用于解释函数式编程的符号的解释/摘要,特别是 Ramda.js?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

JavaScript 函数式编程库 Ramda.js 的 API 文档包含符号缩写,但未提供用于理解这些缩写的图例.有没有我可以去破译这些的地方(网站、文章、备忘单等)?

The API documentation for the JavaScript functional programming library Ramda.js contains symbolic abbreviations but does not provide a legend for understanding these. Is there a place (website, article, cheatsheet, etc.) that I can go to to decipher these?

Ramda.js API 文档中的一些示例:

Some examples from the Ramda.js API documentation:

Number -> Number -> Number
Apply f => f (a -> b) -> f a -> f b
Number -> [a] -> [[a]]
(*... -> a) -> [*] -> a
{k: ((a, b, ..., m) -> v)} -> ((a, b, ..., m) -> {k: v})
Filterable f => (a -> Boolean) -> f a -> f a
Lens s a = Functor f => (a -> f a) -> s -> f s
(acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
(Applicative f, Traversable t) => (a -> f a) -> t (f a) -> f (t a)

我目前能够理解 Ramda.js 正在尝试做的大部分事情,并且我经常可以做出有根据的猜测上述语句的含义.但是,我确信如果我更好地理解这些符号/陈述,我会更容易理解.我想了解各个组件的含义(例如特定的字母、关键字、不同的箭头类型、标点符号等).我也想知道如何阅读"这些行.

I am currently able to understand much of what Ramda.js is trying to do, and I can often make an educated guess what statements like the above mean. However I'm certain I would understand more easily if I understood these symbols/statements better. I would like to understand what individual components mean (e.g. specific letters, keywords, different arrow types, punctuation, etc.). I would also like to know how to "read" these lines.

我在谷歌上搜索或搜索 StackExchange 都没有成功.我使用了Ramda"、函数式编程"、符号"、缩写"、速记"等的各种组合.我也不确定我是否正在寻找 (A) 通用缩写更广泛的函数式编程领域(或者甚至可能只是一般的编程),或者(B)Ramda 作者正在为他们的库使用(或者可能从其他地方选择但进一步修改)的专门语法.

I haven't had success googling this or searching StackExchange. I have used various combinations of "Ramda", "functional programming", "symbols", "abbreviations", "shorthand", etc. I'm also not exactly sure whether I'm looking for (A) universally used abbreviations in the broader field of functional programming (or perhaps even just programming in general), or (B) a specialized syntax that the Ramda authors are using (or perhaps co-opting from elsewhere but modifying further) just for their library.

推荐答案

来自 Ramda Wiki:

(第 1/2 部分 - 对于单个 SO 答案来说太长了!)

(Part 1 / 2 -- too long for a single SO answer!)

(或那些有趣的箭头是关于什么的?")

查看 Ramda 的 over 函数的文档,我们首先看到的是两行如下所示:

Looking at the documentation for Ramda's over function, the first thing we see are two lines that look like this:

Lens s a -> (a -> a) -> s -> s
Lens s a = Functor f => (a -> f a) -> s -> f s

对于从其他 FP 语言来到 Ramda 的人来说,这些可能看起来熟悉,但对 Javascript 开发人员来说,他们可能是纯粹的狼吞虎咽.这里我们描述了如何在 Ramda 文档中阅读这些以及如何将它们用于您自己的代码.

For people coming to Ramda from other FP languages, these probably look familiar, but to Javascript developers, they can be pure gobbledy-gook. Here we describe how to read these in the Ramda documentation and how to use them for your own code.

最后,一旦我们了解如何这些工作,我们将调查为什么人们会想要它们.

And at the end, once we understand how these work, we will investigate why people would want them.

许多受ML影响的语言,包括Haskell,使用一个描述其函数签名的标准方法.作为函数式编程在 Javascript 中变得越来越普遍,这种风格签名正逐渐成为标准.我们借用和改编Ramda 的 Haskell 版本.

Many ML-influenced languages, including Haskell, use a standard method of describing the signatures of their functions. As functional programming becomes more common in Javascript, this style of signatures is slowly becoming almost standard. We borrow and adapt the Haskell version for Ramda.

我们不会尝试创建正式的描述,而只是简单地捕捉到通过例子了解这些签名的本质.

We will not try to create a formal description, but simply capture to the essence of these signatures through examples.

// length :: String -> Number
const length = word => word.length;
length('abcde'); //=> 5

这里我们有一个简单的函数,length,它接受一个字,类型String,并返回字符串中的字符数,这是一个号码.函数上方的注释是签名行.开始函数名,然后是分隔符::",然后是功能的实际描述.应该很清楚该描述的语法是.提供函数的输入,然后是一个箭头,然后是输出.你通常会看到写着的箭头如上,->",在源代码中,在输出中为"文档.它们的意思完全相同.

Here we have a simple function, length, that accepts a word, of type String, and returns the count of characters in the string, which is a Number. The comment above the function is a signature line. It starts with the name of the function, then the separator "::" and then the actual description of the functions. It should be fairly clear what the syntax of that description is. The input of the function is supplied, then an arrow, then the output. You will generally see the arrow written as above, "->", in source code, and as "" in output documentation. They mean exactly the same thing.

我们在箭头前后放置的是Types参数,而不是它们的名称.在这个级别的描述中,我们真的已经说过这是一个接受字符串并返回一个的函数数量.

What we put before and after the arrow are the Types of the parameters, not their names. At this level of description, all we really have said is that this is a function that accepts a String and returns a Number.

// charAt :: (Number, String) -> String
const charAt = (pos, word) => word.charAt(pos); charAt(9, 'mississippi'); //=> 'p'

在这个函数中,函数接受两个参数,一个位置——也就是一个 Number -- 和一个单词 -- 这是一个 String -- 它返回一个单字符String 或空的String.

In this one, the function accepts two parameters, a position -- which is a Number -- and a word -- which is a String -- and it returns a single-character String or the empty String.

在 Javascript 中,与 Haskell 不同,函数可以接受多个范围.为了显示需要两个参数的函数,我们将两个输入参数用逗号并将组括在括号中:(数字,字符串).与许多语言一样,Javascript 函数参数是位置性的,所以顺序很重要.(String, Number) 有完全不同的意思.

In Javascript, unlike in Haskell, functions can accept more than a single parameter. To show a function which requires two parameters, we separate the two input parameters with a comma and wrap the group in parentheses: (Number, String). As with many languages, Javascript function parameters are positional, so the order matters. (String, Number) has an entirely different meaning.

当然对于一个带三个参数的函数,我们只是扩展括号内的逗号分隔列表:

Of course for a function that takes three parameters, we just extend the comma-separated list inside the parentheses:

// foundAtPos :: (Number, String, String) -> Boolean
const foundAtPos = (pos, char, word) => word.charAt(pos) === char;
foundAtPos(6, 's', 'mississippi'); //=> true

对于任何更大的有限参数列表也是如此.

And so too for any larger finite list of parameters.

注意 ES6 样式箭头之间的平行可能会有所启发函数定义和这些类型声明.函数定义通过

It might be instructive to note the parallel between the ES6-style arrow function definition and these type declarations. The function is defined by

(pos, word) => word.charAt(pos);

通过用它们的类型替换参数名称,主体用类型它返回的值和粗箭头=>",带有一个细箭头,->",我们得到签名:

By replacing the argument names with their types, the body with the type of value it returns and the fat arrow, "=>", with a skinny one, "->", we get the signature:

// (Number, String) -> String

值列表

我们经常使用相同类型的值列表.要是我们想要一个函数来添加列表中的所有数字,我们可以使用:

Lists of Values

Very often we work with lists of values, all of the same type. If we wanted a function to add all the numbers in a list, we might use:

// addAll :: [Number] -> Number
const addAll = nbrs => nbrs.reduce((acc, val) => acc + val, 0);
addAll([8, 6, 7, 5, 3, 0, 9]); //=> 38

这个函数的输入是一个NumberList.有一个单独的关于我们所说的列表的讨论,但现在,我们可以本质上认为它们好像是数组.描述一个列表对于给定类型,我们将该类型名称包裹在方括号中,[ ]".一个列表Strings 将是 [String]Booleans 的列表将是[Boolean]NumberLists 的 List 将是 [[Number]].

The input to this function is a List of Numbers. There is a separate discussion on precisely what we mean by Lists, but for now, we can think of it essentially as though they were Arrays. To describe a List of a given type, we wrap that type name in square braces, "[ ]". A List of Strings would be [String], a list of Booleans would be [Boolean], a List of Lists of Numbers would be [[Number]].

当然,这样的列表也可以是函数的返回值:

Such lists can be the return values from a function, too, of course:

// findWords :: String -> [String]
const findWords = sentence => sentence.split(/s+/);
findWords('She sells seashells by the seashore');
//=> ["She", "sells", "seashells", "by", "the", "seashore"]

当我们意识到我们可以将这些结合起来时,我们应该不会感到惊讶:

And we should not be surprised to realize that we can combine these:

// addToAll :: (Number, [Number]) -> [Number]
const addToAll = (val, nbrs) => nbrs.map(nbr => nbr + val);
addToAll(10, [2, 3, 5, 7]); //=> [12, 13, 15, 17]

这个函数接受一个Numberval和一个Number的列表,nbrs,并返回一个新的 Numbers 列表.

This function accepts a Number, val, and a list of Numbers, nbrs, and returns a new list of Numbers.

认识到这就是签名告诉我们的全部很重要.没有办法区分这个功能,仅凭签名,从碰巧接受 Number 和一个列表的任何其他函数Numbers 并返回 Numbers 的列表.[^theorems]

It's important to realize that this is all the signature tells us. There is no way to distinguish this function, by the signature alone, from any other function which happens to accept a Number and a list of Numbers and return a list of Numbers.[^theorems]

[^theorems]:嗯,我们还可以收集到其他信息,在签名暗示的自由定理的形式.

[^theorems]: Well, there is other information we can glean, in the form of the free theorems the signature implies.

还有一种非常重要的类型我们还没有真正讨论过.函数式编程是关于函数的;我们将函数传递为参数和接收函数作为其他函数的返回值职能.我们也需要表示这些.

There is still one very important type we haven't really discussed. Functional programming is all about functions; we pass functions as parameters and receive functions as the return value from other functions. We need to represent these as well.

事实上,我们已经看到了如何表示函数.每一个签名行记录了一个特定的功能.我们重用上面的技术签名中使用的高阶函数的小函数.

In fact, we've already seen how we represent functions. Every signature line documented a particular function. We reuse the technique above in the small for the higher-order functions used in our signatures.

// applyCalculation :: ((Number -> Number), [Number]) -> [Number]
const applyCalculation = (calc, nbrs) => nbrs.map(nbr => calc(nbr));
applyCalculation(n => 3 * n + 1, [1, 2, 3, 4]); //=> [4, 7, 10, 13]

这里的函数calc是由(Number → Number)来描述的,它是就像我们的顶级函数签名一样,只是包裹在括号以将其正确分组为一个单独的单位.我们可以做到与从另一个函数返回的函数相同的事情:

Here the function calc is described by (Number → Number) It is just like our top-level function signatures, merely wrapped in parentheses to properly group it as an individual unit. We can do the same thing with a function returned from another function:

// makeTaxCalculator :: Number -> (Number -> Number)
const makeTaxCalculator = rate => base =>
    Math.round(100 * base + base * rate) / 100;
const afterSalesTax = makeTaxCalculator(6.35); // tax rate: 6.35%
afterSalesTax(152.83); //=> 162.53

makeTaxCalculator 接受以百分比表示的税率(类型Number,并返回一个新函数,它本身接受一个Number并返回一个 Number.再次,我们描述由返回的函数(Number → Number),使得整个函数的签名号码→(号码→号码).

makeTaxCalculator accepts a tax rate, expressed as a percentage (type Number, and returns a new function, which itself accepts a Number and returns a Number. Again, we describe the function returned by (Number → Number), which makes the signature of the whole function Number → (Number → Number).

使用 Ramda,我们可能不会准确地编写 makeTaxCalculator像那样.咖喱是 Ramda 的核心,我们可能会在这里利用它.[^curry-desc]

Using Ramda, we would probably not write a makeTaxCalculator exactly like that. Currying is central to Ramda, and we would probably take advantage of it here.[^curry-desc]

相反,在 Ramda 中,人们很可能会写一个柯里化的 calculateTax可以像 makeTaxCalculator 一样使用的函数,如果是这样的话你想要什么,但也可以一次性使用:

Instead, in Ramda, one would most likely write a curried calculateTax function that could be used exactly like makeTaxCalculator if that's what you wanted, but could also be used in a single pass:

// calculateTax :: Number -> Number -> Number
const calculateTax = R.curry((rate,  base) =>
    Math.round(100 * base + base * rate) / 100);
const afterSalesTax = calculateTax(6.35); // tax rate: 6.35%
afterSalesTax(152.83); //=> 162.53
  // OR 
calculateTax(8.875, 49.95); //=> 54.38

这个柯里化函数可以通过提供两个参数来使用前面并返回一个值,或者只提供一个值并获得返回一个正在寻找第二个函数的函数.为此,我们使用数字→数字→数字.在 Haskell 中,歧义得到解决很简单:箭头绑定在右边,所有函数都需要一个单个参数,虽然有一些语法上的花招让人感觉好像可以用多个参数调用它们.

This curried function can be used either by supplying both parameters up front and getting back a value, or by supplying just one and getting back a function that is looking for the second one. For this we use Number → Number → Number. In Haskell, the ambiguity is resolved quite simply: the arrows bind to the right, and all functions take a single parameter, although there is some syntactic sleight of hand to make it feel as though you can call them with multiple parameters.

在 Ramda 中,直到我们调用函数时,歧义才会解决.什么时候我们调用 calculateTax(6.35),因为我们选择不提供第二个参数,我们取回最后的 Number → Number 部分签名.当我们调用 calculateTax(8.875, 49.95) 时,我们提供了前两个 Number 参数,所以只取回最后的号码.

In Ramda, the ambiguity is not resolved until we call the function. When we call calculateTax(6.35), since we have chosen not to supply the second parameter, we get back the final Number → Number part of the signature. When we call calculateTax(8.875, 49.95), we have supplied the first two Number parameters, and so get back only the final Number.

柯里化函数的签名总是这样的,一个序列由''s 分隔的类型.因为其中一些类型可能本身是函数,可能有括号子结构它们本身有箭头.这完全可以接受:

The signatures of curried functions always look like this, a sequence of Types separated by ''s. Because some of those types might themselves be functions, there might be parenthesized substructures which themselves have arrows. This would be perfectly acceptable:

// someFunc :: ((Boolean, Number) -> String) -> (Object -> Boolean) ->
//             (Object -> Number) -> Object -> String

这是编的.我没有一个真正的功能可以指向这里.但是我们可以从它的类型签名中了解一些关于此类函数的信息.它接受三个函数和一个 Object 并返回一个 String.这它接受自己的第一个函数需要一个 Boolean 和一个 Number 和返回一个 String.请注意,这里没有将其描述为咖喱函数(或者它会被写成 (Boolean → Number →String).) 第二个函数参数接受一个 Object 并返回一个 Boolean,第三个接受一个 Object 并返回一个 Number.

This is made up. I don't have a real function to point to here. But we can learn a fair bit about such a function from its type signature. It accepts three functions and an Object and returns a String. The first function it accepts itself takes a Boolean and a Number and returns a String. Note that this is not described here as a curried function (or it would have been written as (Boolean → Number → String).) The second function parameter accepts an Object and returns a Boolean, and the third accepts an Object and returns a Number.

这只是比 Ramda 函数中的实际情况稍微复杂一些.我们不经常有四个参数的函数,我们当然没有有任何接受三个函数参数的.所以如果这一点很清楚,我们正在了解 Ramda 必须抛出的任何内容我们.

This is only slightly more complex than is realistic in Ramda functions. We don't often have functions of four parameters, and we certainly don't have any that accept three function parameters. So if this one is clear, we're well on our way to understanding anything Ramda has to throw at us.

[^curry-desc]:对于来自其他语言的人,Ramda 的柯里化可能与您习惯的有些不同: If f ::(A, B, C) → Dg = curry(f),然后 g(a)(b)(c) == g(a)(b, c) ==g(a, b)(c) == g(a, b, c) == f(a, b, c).

[^curry-desc]: For people coming from other languages, Ramda's currying is perhaps somewhat different than you're used to: If f :: (A, B, C) → D and g = curry(f), then g(a)(b)(c) == g(a)(b, c) == g(a, b)(c) == g(a, b, c) == f(a, b, c).

如果您使用过 map,就会知道它相当灵活:

If you've worked with map, you'll know that it's fairly flexible:

map(word => word.toUpperCase(), ['foo', 'bar', 'baz']); //=> ["FOO", "BAR", "BAZ"]
map(word => word.length, ['Four', 'score', 'and', 'seven']); //=> [4, 5, 3, 5]
map(n => n * n, [1, 2, 3, 4, 5]); //=> [1, 4, 9, 16, 25]
map(n => n % 2 === 0, [8, 6, 7, 5, 3, 0, 9]); //=> [true, true, false, false, false, true, false]

由此,我们希望将以下所有类型签名应用于地图:

From this, we would want to apply all the following type signatures to map:

// map :: (String -> String) -> [String] -> [String]
// map :: (String -> Number) -> [String] -> [Number]
// map :: (Number -> Number) -> [Number] -> [Number]
// map :: (Number -> Boolean) -> [Number] -> [Boolean]

但显然还有更多的可能性.我们不能简单地列出商场.为了解决这个问题,类型签名不仅处理具体的NumberStringObject 等类,但也有泛型类的表示.

But clearly there are many more possibilities too. We cannot simply list them all. To deal with this, type signatures deal not only with concrete classes such as Number, String, and Object, but also with representations of generic classes.

我们如何描述map?这相当简单.第一个参数是一个函数,它接受一个类型的元素,并返回一个元素第二种.(这两种类型不必不同.)第二个参数是输入类型的元素列表功能.它返回该输出类型的元素列表功能.

How would we describe map? It's fairly simple. The first parameter is a function that takes an element of one type, and returns an element of a second type. (The two type don't have to have to be different.) The second parameter is a list of elements of the input type of that function. It returns a list of elements of the output type of that function.

我们可以这样描述它:

// map :: (a -> b) -> [a] -> [b]

我们使用通用占位符,而不是具体类型代表任意类型的小写字母.

Instead of the concrete types, we use generic placeholders, single lower-character letters to stand for arbitrary types.

很容易将这些与具体类型区分开来.那些是完整的单词,并按照惯例大写.通用类型变量只是 abc 等.偶尔,如果有充分的理由,我们可能会使用字母表后面的字母,如果它有助于使一些了解泛型可能代表什么类型的类型(想想 kv 用于 keyvaluen 用于数字),但大多数情况下我们只是使用这些来自字母表的开头.

It's easy enough to distinguish these from the concrete types. Those are full words, and by convention are capitalized. Generic type variables are just a, b, c, etc. Occasionally, if there is a strong reason, we might use a letter from later in the alphabet if it helps makes some sense of what sorts of types the generic might represent (think k and v for key and value or n for a number), but mostly we just use these ones from the beginning of the alphabet.

请注意,一旦在签名中使用了泛型类型变量,它表示对于同一变量的所有使用都是固定的值.我们不能在签名的某一部分使用 b 然后在其他地方重用除非两者在整个签名中都必须是相同的类型.此外,如果签名中的两种类型必须相同,那么我们有为它们使用相同的变量.

Note that once a generic type variable is used in a signature, it represents a value that is fixed for all uses of that same variable. We can't use b in one part of the signature and then reuse it elsewhere unless both have to be of the same type in the entire signature. Moreover, if two types in the signature must be the same, then we have to use the same variable for them.

但是没有什么可以说两个不同的变量有时不能指向相同的类型.map(n => n * n, [1, 2, 3]);//=>[1, 4, 9](Number → Number) → [Number] → [Number],所以如果我们要匹配(a → b) → [a] → [b],那么ab都指向Number.这不是问题.我们仍然有两个不同的类型变量,因为会有不一样的情况.

But there is nothing to say that two different variables can't sometimes point to the same types. map(n => n * n, [1, 2, 3]); //=> [1, 4, 9] is (Number → Number) → [Number] → [Number], so if we're to match (a → b) → [a] → [b], then both a and b point to Number. This is not a problem. We still have two different type variables since there will be cases where they are not the same.

有些类型更复杂.我们可以很容易地想象一个代表一个的类型类似物品的集合,我们称之为 Box.但没有实例是任意 Box;每个只能容纳一种物品.什么时候我们讨论 Box 我们总是需要指定一个 Box 的东西.

Some types are more complex. We can easily imagine a type representing a collection of similar items, let's call it a Box. But no instance is an arbitrary Box; each one can only hold one sort of item. When we discuss a Box we always need to specify a Box of something.

// makeBox :: Number -> Number -> Number -> [a] -> Box a
const makeBox = curry((height, width, depth, items) => /* ... */);

// addItem :: a -> Box a -> Box a
const addItem = curry((item, box) => /* ... */);

这是我们如何指定一个由未知类型 a 参数化的 Box:框一个.这可以在我们需要类型的任何地方使用,作为参数或作为函数的返回.当然我们可以参数化类型还有一种更具体的类型,Box CandyBox Rock.(虽然这是合法的,我们目前实际上并没有在 Ramda 中这样做.也许我们只是不想被指责像一盒石头一样愚蠢.)

This is how we specify a Box parameterized by the unknown type a: Box a. This can be used wherever we need a type, as a parameter or as the return of a function. Of course we could parameterize the type with a more specific type as well, Box Candy or Box Rock. (Although this is legitimate, we don't actually do this in Ramda at the moment. Perhaps we simply don't want to be accused of being as dumb as a box of rocks.)

不必只有一个类型参数.我们可能有一个Dictionary 类型,在两个键的类型上参数化以及它使用的值的类型.这可以写成 Dictionary kv.这也展示了我们可能使用 single 的地方不是字母表中首字母的字母.

There does not have to be just a single type parameter. We might have a Dictionary type that is parameterized over both the type of the keys and the type of the values it uses. This could be written Dictionary k v. This also demonstrates the sort of place where we might use single letters that are not the initial ones from the alphabet.

Ramda 本身并没有很多这样的声明,但是我们可能会发现自己在自定义代码中经常使用这些东西.这这些最大的用途是支持类型类,所以我们应该描述

There aren't many declarations like this in Ramda itself, but we might find ourselves using such things fairly often in custom code. The largest usage of these is to support typeclasses, so we should describe those.

有时我们的类型会失控,工作变得困难和他们在一起是因为他们内在的复杂性或者因为他们太通用的.Haskell 允许使用类型别名来简化理解这些.Ramda 也借用了这个概念,尽管它被使用有节制.

Sometimes our types get out of hand, and it becomes difficult to work with them because of their inner complexity or because they're too generic. Haskell allows for type aliases to simplify the understanding of these. Ramda borrows this notion as well, although it's used sparingly.

这个想法很简单.如果我们有一个参数化类型User String,其中String 是用来代表一个名字的,我们想要更多特定于生成时表示的 String 类型URL,我们可以创建一个这样的类型别名:

The idea is simple. If we had a parameterized type User String, where the String was meant to represent a name, and we wanted to be more specific about the type of String that is represented when generating a URL, we could create a type alias like this:

// toUrl :: User Name u => Url -> u -> Url
//     Name = String
//     Url = String
const toUrl = curry((base, user) => base +
user.name.toLowerCase().replace(/W/g, '-'));
toUrl('http://example.com/users/', {name: 'Fred Flintstone', age: 24});
//=> 'http://example.com/users/fred-flintstone'

别名NameUrl 出现在="的左侧.他们的右侧显示等效值.

The aliases Name and Url appear to the left of an "=". Their equivalent values appear to the right.

如前所述,这也可以用来创建一个简单的别名到更多复杂类型.Ramda 中的许多函数与 Lenses 一起工作,并且通过使用类型别名简化了这些类型:

As noted, this can also be used to create a simple aliases to a more complex type. A number of functions in Ramda work with Lenses, and the types for those are simplified by using a type alias:

//     Lens s a = Functor f => (a -> f a) -> s -> f s

我们稍后会尝试分解这个复杂的值,但现在,应该很清楚,无论 Lens s a 代表什么,在它下面只是复杂表达式的别名,Functorf ⇒ (a → f a) → s → f s.

We'll try to break down that complex value a little later, but for now, it should be clear enough that whatever Lens s a represents, underneath it is just an alias for the complicated expression, Functor f ⇒ (a → f a) → s → f s.

(单独答案中的第 2 部分.)

(Part 2 in a separate answer.)

这篇关于我在哪里可以找到用于解释函数式编程的符号的解释/摘要,特别是 Ramda.js?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆