gpt4 book ai didi

javascript - 在哪里可以找到用于解释功能编程的符号的解释/摘要,特别是Ramda.js?

转载 作者:行者123 更新时间:2023-12-04 01:01:22 28 4
gpt4 key购买 nike

JavaScript功能编程库Ramda.js的API文档包含符号缩写,但未提供用于了解这些符号的图例。我可以在某个地方(网站,文章,备忘单等)破译这些内容吗?

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

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想要做什么的大部分事情,而且我经常可以做出有根据的猜测,例如上述意思是什么。但是,我敢肯定,如果我更好地理解这些符号/陈述,我会更容易理解。我想了解各个组成部分的含义(例如,特定的字母,关键字,不同的箭头类型,标点符号等)。我还想知道如何“阅读”这些行。

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

最佳答案

从Ramda Wiki:

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

类型签名

(或“所有这些有趣的箭头是什么?”)

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

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

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

最后,一旦我们了解了这些工作原理,我们将进行调查
人们为什么要他们。

命名类型

许多受 ML影响的语言,包括 Haskell,都使用
描述其功能签名的标准方法。如
函数式编程在Javascript中变得越来越普遍,这种风格的
签名正逐渐变得几乎是标准的。我们借用并适应
Haskell版本的Ramda。

我们不会尝试创建正式的描述,而只是捕获到
通过示例了解这些签名的实质。
// length :: String -> Number
const length = word => word.length;
length('abcde'); //=> 5

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

我们在箭头前后放置的是
参数,而不是它们的名称。在这种描述水平上,我们真正
曾经说过,这是一个接受String并返回a的函数
数。
// charAt :: (Number, String) -> String
const charAt = (pos, word) => word.charAt(pos); charAt(9, 'mississippi'); //=> 'p'

在这个函数中,该函数接受两个参数,一个位置-
一个 Number-一个单词-这是一个 String-它返回一个
单字符 String或空 String

在Javascript中,与Haskell不同,函数可以接受多个
参数。为了显示一个需要两个参数的函数,我们将
用逗号将两个输入参数括起来,然后将组用括号括起来: (Number, String)。与许多语言一样,JavaScript函数
参数是位置参数,因此顺序很重要。 (String, Number)
完全不同的意思

当然,对于具有三个参数的函数,我们只需扩展
括号内用逗号分隔的列表:
// foundAtPos :: (Number, String, String) -> Boolean
const foundAtPos = (pos, char, word) => word.charAt(pos) === char;
foundAtPos(6, 's', 'mississippi'); //=> true

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

注意ES6样式的箭头之间的相似之处可能是有益的
函数定义和这些类型声明。功能定义
通过
(pos, word) => word.charAt(pos);

通过将参数名称替换为其类型,将主体替换为
它返回的值的类型和粗箭头“ =>”,以及一个瘦小的箭头,
->”,我们获得签名:
// (Number, String) -> String

值 list

通常,我们使用所有相同类型的值列表。要是我们
想要一个将所有数字加到列表中的函数,我们可以使用:
// addAll :: [Number] -> Number
const addAll = nbrs => nbrs.reduce((acc, val) => acc + val, 0);
addAll([8, 6, 7, 5, 3, 0, 9]); //=> 38

该函数的输入是 Number的列表。有一个单独的
关于 what we mean by Lists的讨论,但是现在,我们可以
认为它们本质上就像是数组。描述 list
对于给定类型,我们将该类型名称包装在方括号“ [ ]”中。一个列表
String将为 [String],列表的 Boolean将为 [Boolean]Number的列表列表将是 [[Number]]

当然,此类列表也可以是函数的返回值:
// findWords :: String -> [String]
const findWords = sentence => sentence.split(/\s+/);
findWords('She sells seashells by the seashore');
//=> ["She", "sells", "seashells", "by", "the", "seashore"]

我们可以结合使用以下这些功能,我们并不感到惊讶:
// addToAll :: (Number, [Number]) -> [Number]
const addToAll = (val, nbrs) => nbrs.map(nbr => nbr + val);
addToAll(10, [2, 3, 5, 7]); //=> [12, 13, 15, 17]

此函数接受 NumbervalNumber列表, nbrs,并返回 Number的新列表。

重要的是要意识到这就是签名告诉我们的全部。
无法仅通过签名来区分此功能,
碰巧接受 Number和列表的其他任何函数 Number,并返回 Number的列表。[^ theorems]

[^定理]:嗯,我们可以收集其他信息
签名暗示的 free theorems的形式。

职能

我们还没有真正讨论过一种非常重要的类型。
函数式编程全都与函数有关。我们将函数传递为
参数并接收函数作为其他函数的返回值
职能。我们也需要代表这些。

实际上,我们已经了解了如何表示函数。每个签名
行记录了特定的功能。我们在
较小,用于我们签名中使用的高阶函数。
// 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)描述。
就像我们的顶级函数签名一样
括号以将其适当地分组为单个单位。我们可以做
从另一个函数返回的函数也是如此:
// 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),使整个功能签名 Number → (Number → Number)

curry

使用Ramda,我们可能不会精确地编写 makeTaxCalculator像那样。 curry 对于Ramda至关重要,我们可能会考虑
这里的优势。[^ curry-desc]

取而代之的是,在Ramda中,最有可能编写一个 curry calculateTax可以像 makeTaxCalculator一样使用的功能,如果
您想要的东西,但也可以一次使用:
// 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

可以通过同时提供两个参数来使用此 curry 函数
前端并获取值(value),或仅提供一个并获取
返回正在寻找第二个功能的功能。为此,我们使用 Number → Number → Number。在Haskell中,歧义得以解决
很简单:箭头绑定(bind)到右边,所有功能都取一个
单个参数,尽管语法上有些技巧
使您感觉好像可以使用多个参数来调用它们。

在Ramda中,直到我们调用该函数,歧义才得以解决。什么时候
我们称为 calculateTax(6.35),因为我们选择不提供
第二个参数,我们返回最后的 Number → Number部分
签名。当我们调用 calculateTax(8.875, 49.95)时,我们已经提供了
前两个 Number参数,因此仅返回最后一个 Number

curry 函数的签名总是看起来像这样,
类型之间用 分隔。因为其中一些类型可能
本身就是函数,可能会有带括号的子结构
本身有箭头。这将是完全可以接受的:
// someFunc :: ((Boolean, Number) -> String) -> (Object -> Boolean) ->
// (Object -> Number) -> Object -> String

这是组成的。我没有真正的功能可以指向这里。但是我们
可以从类型签名中了解有关此函数的知识。它
接受三个函数和一个 Object并返回一个 String。的
它接受自身的第一个函数采用 BooleanNumber
返回 String。请注意,此处未将其描述为 curry
函数(或者将其写为 (Boolean → Number →
String)
。)第二个函数参数接受 Object并返回 Boolean,第三个接受 Object并返回 Number

这仅比Ramda函数中的实际情况稍微复杂一点。
我们通常没有四个参数的函数,而且我们当然也没有
具有接受三个功能参数的任何参数。因此,如果这一点很清楚,
我们在理解Ramda所面临的任何挑战方面都做得很好
我们。

[^ curry-desc]:对于来自其他语言的人,Ramda的
currying可能与您习惯的有所不同:如果是 f ::
(A, B, C) → D
g = curry(f),则为 g(a)(b)(c) == g(a)(b, c) ==
g(a, b)(c) == g(a, b, c) == f(a, b, c)


类型变量

如果您使用过 map,那么您会知道它非常灵活:
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]

由此,我们希望将以下所有类型签名应用于
map :
// map :: (String -> String) -> [String] -> [String]
// map :: (String -> Number) -> [String] -> [Number]
// map :: (Number -> Number) -> [Number] -> [Number]
// map :: (Number -> Boolean) -> [Number] -> [Boolean]

但显然还有更多的可能性。我们不能简单地列出
商场。为了解决这个问题,类型签名不仅要处理具体的
诸如 NumberStringObject之类的类,但也包含
通用类的表示形式。

我们将如何描述 map?这很简单。第一个参数是
一个函数,该函数采用一种类型的元素,然后返回
第二种。 (两种类型不必一定要不同。)
第二个参数是输入类型的元素列表
功能。它返回该输出类型的元素列表
功能。

我们可以这样描述它:
// map :: (a -> b) -> [a] -> [b]

代替具体类型,我们使用通用占位符,单个
小写字母代表任意类型。

容易将它们与具体类型区分开来。那些是
完整的单词,并且按照惯例大写。通用类型变量
只是 abc等。有时,如果有很强的理由,
我们可以使用字母表后面的字母,如果有帮助的话
泛型可能代表哪种类型的感觉(想想 kv表示 keyvaluen表示数字),但大多数情况下,我们只使用
这些都是从字母开头开始的。

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

但是没有什么可以说两个不同的变量有时不能
指向相同的类型。 map(n => n * n, [1, 2, 3]); //=> [1, 4, 9](Number → Number) → [Number] → [Number],所以如果我们要匹配 (a → b) → [a] → [b],然后 ab都指向 Number
这不是问题。自从我们还有两个不同的类型变量
在某些情况下,它们可能会不同。

参数化类型

有些类型更复杂。我们可以轻松想象一个代表
相似项目的集合,我们称之为 Box。但是没有实例是
任意 Box;每个只能容纳一种物品。什么时候我们
讨论我们总是需要指定某种内容的 BoxBox
// 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) => /* ... */);

这就是我们指定由未知类型 Box参数化的 a的方式: Box a。可在需要类型,参数或参数的任何地方使用
函数的返回。当然,我们可以使用
更加具体的类型 Box CandyBox Rock。 (尽管这
是合法的,目前我们实际上不在Ramda中这样做。也许
我们只是不想被指控像一盒石头一样愚蠢。)

不必只是一个类型参数。我们可能有一个
通过两种键的类型进行参数化的 Dictionary类型
以及它使用的值的类型。这可以写成 Dictionary k
v
。这也说明了我们可能会使用单个
不是字母首字母的字母。

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

类型别名

有时我们的类型变得一发不可收拾,并且变得很难工作
因为他们内在的复杂性或因为他们太过
通用。 Haskell允许使用类型别名来简化理解
这些。尽管使用了Ramda,但它也借鉴了这一概念
谨慎地。

这个想法很简单。如果我们有一个参数化类型 User String,其中
字符串是用来代表一个名字的,我们希望更多
特定于生成字符串时表示的字符串类型
URL,我们可以这样创建类型别名:
// 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出现在“ =”的左侧。其
等效值显示在右侧。

如前所述,这也可以用来为更多的对象创建简单的别名。
复杂类型。 Ramda中的许多功能都可以使用 Lens es和
通过使用类型别名简化了这些类型:
//     Lens s a = Functor f => (a -> f a) -> s -> f s

稍后,我们将尝试分解这种复杂的值(value),但现在,
应该足够清楚,无论 Lens s a代表什么,
在下面,它只是复杂表达式 Functor
f ⇒ (a → f a) → s → f s
的别名。

( separate answer的第2部分。)

关于javascript - 在哪里可以找到用于解释功能编程的符号的解释/摘要,特别是Ramda.js?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40361059/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com