我在哪里可以找到用于解释函数式编程的符号的解释/摘要,特别是Ramda.js?

时间:2016-11-01 13:28:01

标签: javascript functional-programming symbols ramda.js

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","功能编程","符号","缩写","简写&#34的各种组合我等也不确定我是否正在寻找(A)在更广泛的函数式编程领域(或者甚至只是一般的编程)中普遍使用的缩写,或者(B)一个专门的Ramda作者正在使用的语法(或者可能是从别处合作,但是进一步修改)只是为了他们的库。

3 个答案:

答案 0 :(得分:5)

来自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开发人员来说,他们可以是纯粹的gobbledy-gook。 在这里,我们将介绍如何在Ramda文档中阅读这些内容以及如何阅读 将它们用于您自己的代码。

最后,一旦我们了解 这些工作的方式,我们就会进行调查 为什么人们会想要它们。

命名类型

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

我们不会尝试创建正式的描述,而只是捕获到 这些签名的实质是通过例子。

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

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

我们在箭头之前和之后放置的是 Types 参数,而不是他们的名字。在这个级别的描述,我们真的 已经说过这是一个接受String并返回一个的函数 号。

// 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);

通过用它们的类型替换参数名称,用 它返回的值的类型和胖箭头,#34; =>",一个瘦的, " ->",我们得到签名:

// (Number, String) -> String

值列表

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

// 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,但就目前而言,我们可以 把它想象成它们就像是阵列一样。描述列表 对于给定类型,我们将该类型名称包装在方括号中," [ ]"。一个列表 String的{​​{1}}将是[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]

我们不应该意识到我们可以将这些结合起来:

Number

此功能接受valNumbernbrs列表, Number,并返回Number s。

的新列表

重要的是要意识到签名告诉我们这是 all 。 仅通过签名无法区分此功能, 来自任何其他恰好接受Number和列表的函数 Number并返回// 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] s列表。[^定理]

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

功能

还有一个非常重要的类型我们还没有真正讨论过。 功能编程完全与功能有关;我们传递函数为 参数和接收函数作为来自其他的返回值 功能。我们也需要代表这些。

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

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),它使整个函数的签名 makeTaxCalculator

柯里

使用Ramda,我们可能不会完全写出calculateTax 像那样。 Currying是Ramda的核心,我们可能会采取 它的优势在这里。[^ curry-desc]

相反,在Ramda中,人们最有可能写出一个有条理的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 一样使用的函数 你想要什么,但也可以一次性使用:

Number → Number → Number

这个curried函数可以通过提供两个参数来使用 前面并找回一个值,或者只提供一个并获得 返回正在寻找第二个的函数。为此,我们使用 calculateTax(6.35)。在Haskell中,歧义得到了解决 非常简单:箭头绑定到右边,所有功能都需要一个 单个参数,虽然有一些句法诡计 让你觉得好像可以用多个参数调用它们。

在Ramda中,在我们调用函数之前,不会解决歧义。什么时候 我们打电话给Number → Number,因为我们选择不提供 第二个参数,我们回到最后的calculateTax(8.875, 49.95)部分 签名。当我们致电Number时,我们已经提供了 前两个Number参数,所以只返回最后一个

curried函数的签名总是这样,一系列的 由' // someFunc :: ((Boolean, Number) -> String) -> (Object -> Boolean) -> // (Object -> Number) -> Object -> String '分隔的类型。因为其中一些类型可能 它们本身就是函数,可能有带括号的子结构 他们自己有箭头。这是完全可以接受的:

Object

这是弥补的。我没有真正的功能指向这里。但我们 可以从类型签名中学到很多关于这种功能的知识。它 接受三个函数和String并返回Boolean。该 它接受的第一个函数需要NumberString和 返回(Boolean → Number → String)。请注意,这里没有描述为咖喱 函数(或者它将被写为Object。)第二个函数参数接受Boolean并返回 Object,第三个接受Number并返回f :: (A, B, C) → D

这比Ramda函数中的实际情况稍微复杂一些。 我们通常不具备四个参数的功能,我们当然不会 有任何接受三个功能参数。所以如果这个很清楚, 我们正在努力理解Ramda必须抛出的任何东西 我们。

[^ curry-desc]:对于来自其他语言的人,Ramda's currying可能与你以前有所不同:如果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 :: (String -> String) -> [String] -> [String]
// map :: (String -> Number) -> [String] -> [Number]
// map :: (Number -> Number) -> [Number] -> [Number]
// map :: (Number -> Boolean) -> [Number] -> [Boolean]

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

Number

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

我们如何描述// map :: (a -> b) -> [a] -> [b] ?这很简单。第一个参数是 一个函数,它接受一种类型的元素,并返回一个元素 第二种类型。 (这两种类型不一定要有所不同。) 第二个参数是该输入类型的元素列表 功能。它返回该输出类型的元素列表 功能

我们可以这样描述它:

a

我们使用通用占位符,而不是具体类型 低字符字母代表任意类型。

将这些与具体类型区分开来很容易。那些是 完整的词语,按照惯例是大写的。通用类型变量 只是bck等。偶尔,如果有充分理由, 我们可能会使用字母表后面的一封信,如果它有帮助的话 通用可能代表什么类型的感觉(想想vkey的{​​{1}}和value的{​​{1}}或n,但我们大多只是使用 这些是从字母表开头的。

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

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

参数化类型

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

// 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允许类型别名以简化理解 这些。拉姆达也借用了这个概念,虽然它被使用了 微

这个想法很简单。如果我们有参数化类型User String,那么 字符串意味着代表一个名字,我们想要更多 具体关于生成a时表示的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

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

separate answer中的第2部分。)

答案 1 :(得分:4)

来自Ramda Wiki

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

类型约束

有时我们想限制我们可以在a中使用的泛型类型 以某种方式签名。我们可能需要maximum函数 可以Numbers上的StringsDates上的Objects,但不能 任意a < b。我们想要描述有序类型,其中包含的类型 Ord将始终返回有意义的结果。我们讨论细节 类型< in the Types section;为了我们的目的,它 足以说它是为了捕捉那些拥有的类型 一些与// maximum :: Ord a => [a] -> a const maximum = vals => reduce((curr, next) => next > curr ? next : curr, head(vals), tail(vals)) maximum([3, 1, 4, 1]); //=> 4 maximum(['foo', 'bar', 'baz', 'qux', 'quux']); //=> 'qux' maximum([new Date('1867-07-01'), new Date('1810-09-16'), new Date('1776-07-04')]); //=> new Date("1867-07-01") 一起使用的排序操作。

=>

此说明[^ maximum-note]在。添加约束部分 开始时,用右双箭头与其余部分分开(&#34; &#34; in 代码,有时&#34; Ord a ⇒ [a] → a&#34;在其他文档中。)Ord 说最大值采用某种类型的元素集合,但那样 类型必须遵守[a]

在动态类型的Javascript中,没有简单的方法来强制执行 这种类型约束而不对每个参数添加类型检查, 甚至每个列表的每个值。[^ strong-types]但这对我们来说是真实的 一般类型签名。当我们在签名中需要[1, 2, 'a', false, undefined, [42, 43], {foo: bar}, new Date, null]时, 我们无法保证用户不会通过我们map。所以我们整个 类型注释是描述性的和有抱负的而不是 编译器强制执行,就像在Haskell中那样。

Ramda函数最常见的类型约束是指定的 通过Javascript FantasyLand specification

当我们之前讨论Tree函数时,我们只讨论了映射 一个值列表上的函数。但是映射的想法更多 一般而言。它可以用来描述一个应用程序 函数包含任意数据结构,包含一些数量的a 某种类型,如果它返回另一个具有new的相同形状的结构 其中的价值观。我们可能会映射DictionaryWrapper,平原 Functor只包含一个值或许多其他类型。

可以映射的东西的概念由a捕获 其他语言和FantasyLand从抽象借用的代数类型 数学,称为Functormap只是一种类型 包含一个map方法,受一些简单的法律约束。拉姆达的map 函数将在我们的类型上调用map方法,假设我们 没有通过列表(或Ramda已知的其他类型)但确实传递了一些东西 如果有Functor,我们希望它的行为类似于// map :: Functor f => (a -> b) -> f a -> f b

为了在签名中描述这一点,我们在其中添加了一个约束部分 签名栏:

// weirdFunc :: (Functor f, Monoid b, Ord b) => (a -> b) -> f a -> f b

注意约束块不必只有一个 对它的约束。我们可以有多个约束,用逗号分隔 并用括号括起来。所以这可能是一些奇怪的签名 功能:

// getIndex :: a -> [a] -> Number
//          :: String -> String -> Number
const getIndex = curry((needle, haystack) => haystack.indexOf(needle));
getIndex('ba', 'foobar'); //=> 3
getIndex(42,  [7, 14, 21, 28, 35, 42, 49]); //=> 5

没有详述它的作用或使用方法MonoidOrd,我们至少可以看到需要提供哪种类型 使此功能正常运行。

[^ maximum-note]:此最大功能存在问题;它 将在空列表中失败。试图解决这个问题需要我们 太远了。

[^ strong-types]:有一些很好的工具可以解决这个问题 Javascript的缺点,包括语言技术,如 Ramda的姐妹项目Sanctuary,Javascript的扩展 更强类型,例如flowTypeScript,以及 更强类型的语言,编译为Javascript,如 ClojureScriptElmPureScript

多个签名

有时而不是试图找到最通用的版本 签名,列出几个相关签名更直接 分别。这些包含在Ramda源代码中,作为两个单独的 JSDoc标签,最终作为文档中的两个不同的行。这个 我们如何在自己的代码中编写一个:

flip

显然,如果我们选择,我们可以做两个以上的签名。但是呢 请注意,这不应该太常见。目标是写签名 通用性足以捕获我们的用法,而不是如此抽象 它们实际上模糊了函数的用法。如果我们可以这样做 单一签名,我们可能应该。如果需要两个,那就这样吧。 但如果我们有很长的签名列表,那么我们可能会错过一个 共同抽象。

Ramda Miscellany

Variadic Functions

从中移植此样式签名涉及多个问题 Haskell到Javascript。 Ramda团队已经在 ad hoc 上解决了这些问题 基础,这些解决方案仍然是subject to change

在Haskell中,所有函数都有一个固定的arity。但Javsacript必须处理 具有可变函数。 Ramda的// flip :: (a -> b -> ... -> z) -> (b -> a -> ... -> z) const flip = fn => function(b, a) { return fn.apply(this, [a, b].concat([].slice.call(arguments, 2))); }; flip((x, y, z) => x + y + z)('a', 'b', 'c'); //=> 'bac' 函数就是一个很好的例子。它&#39; S 一个简单的概念:接受任何函数并返回一个新函数 交换前两个参数的顺序。

...

这[^ flip-example]显示了我们如何处理可变参数的可能性 固定但未知的arity的功能或功能:我们只是使用 省略号(&#34; Any / *&#34;在源代码中,&#34;``&#34;在输出文档中)显示那里 是该签名中缺少的一些不计数的参数。 Ramda 已从其自己的代码库中删除了几乎所有可变参数函数,但是 这就是它处理与之交互的外部函数的方式 他们的签名我们都不知道。

[^ flip-example]:这不是Ramda的实际代码,它交易a 显着提高性能的简单性。

*输入

我们很快就会hoping to change this,但Ramda的类型签名 通常包括星号(Any)或[Any]合成类型。这是 只是一种报告的方式,虽然有参数或回报 在这里,我们可以推断它的实际类型。我们来了 认识到只有一个地方仍然有意义, 这是我们有一个类型列表,其类型可能会有所不同。在那 我们应该报告a。所有其他用途的任意 type可以替换为通用类型名称,例如bObject。这种变化可能随时发生。

简单对象

有几种方法可以选择代表普通的Javascript 对象。很明显,我们可以说Record,但有时也会这样 似乎需要其他东西。当一个对象用作 类似值的字典(与其他角色相反) {k: v}),然后键的类型和值可以变为 相关。在某些签名中,Ramda使用&#34; // keys :: {k: v} -> [k] // values :: {k: v} -> [v] // ... keys({a: 86, b: 75, c: 309}); //=> ['a', 'b', 'c'] values({a: 86, b: 75, c: 309}); //=> [86, 75, 309] &#34;代表这个 一种对象。

// makeObj :: [k,v]] -> {k: v}
const makeObj = reduce((obj, pair) => assoc(pair[0], pair[1], obj), {});
makeObj([['x', 10], ['y', 20]]); //=> {"x": 10, "y": 20}
makeObj([['a', true], ['b', true], ['c', false]]);
//=> {a: true, b: true, c: false}

而且,与往常一样,这些可以用作函数调用的结果 代替:

{k: v}

记录

虽然这可能与Ramda本身并非完全相关,但它是 有时能够区分用作的Javascript对象 记录,而不是用作词典的记录。字典是 更简单,上面的{k: Number}描述可以更具体 需要{k: Rectangle}{String: Number},或者即使我们需要它, 与// display :: {name: String, age: Number} -> (String -> Number -> String) -> String const display = curry((person, formatter) => formatter(person.name, person.age)); const formatter = (name, age) => name + ', who is ' + age + ' years old.'; display({name: 'Fred', age: 25, occupation: 'crane operator'}, formatter); //=> "Fred, who is 25 years old." 等等。我们可以处理类似的记录 我们选择:

over

记录符号看起来很像Object literals,其值为 字段由其类型替换。我们只考虑字段名称 这与我们有某种关系。 (在上面的例子中,即使 我们的数据有一个&#39;职业&#39;字段,它不在我们的签名中,因为 它不能直接使用。

复杂示例:Lens s a -> (a -> a) -> s -> s Lens s a = Functor f => (a -> f a) -> s -> f s

所以在这一点上,我们应该有足够的信息来理解 over函数的签名:

Lens s a = Functor f ⇒ (a → f a) →
s → f s

我们从类型别名Lens开始。这告诉我们类型s由两个参数化 通用变量af。我们知道存在约束 Lens中使用的Functor变量的类型:它必须是Lens。 考虑到这一点,我们发现a是两个人的函数 参数,第一个是泛型​​类型的函数 f a为其中一个参数化类型s,第二个为值 通用类型f s。结果是参数化类型map的值。 做什么我们不知道。我们无法知道。我们的类型 签名告诉我们很多关于功能的信息,但他们没有回答 关于函数实际执行的问题。我们可以假设 某处必须调用f a Functor方法,因为那是。{1}} 只有map类型定义的函数,但我们不知道如何或 调用Lens的原因。不过,我们知道over是一个函数 描述,我们可以用它来指导我们对over的理解。

函数Lens a s被描述为三个函数 参数,刚刚分析的a,来自通用的函数 键入s到同一类型,以及泛型类型s的值。该 整个事物返回over类型的值。

我们可以深入挖掘一下,或许可以进一步推论 maximum必须对收到的类型做什么。有重大意义 研究所谓的自由定理展示不变量 只能从类型签名中派生出来。但是这份文件已经很远了 太长。如果您有兴趣,请参阅进一步阅读。

但是为什么?

现在我们知道如何来读写这些签名。我们为什么 想要,为什么功能程序员如此迷恋它们?

有几个很好的理由。首先,一旦我们习惯了 它们,我们可以从单一行中获得关于函数的大量见解 元数据,没有名称的干扰。名字听起来很不错 想法,直到你意识到别人选择的名字不是 你会选择的名字。上面我们讨论了所谓的函数 &#34; makeObj&#34;和&#34; max&#34;。知道这个是有用还是有点困惑 Ramda,等效函数被称为&#34; fromPairs&#34;和&#34; foo :: Object -> Number &#34;? 参数名称明显更糟糕。当然还有 通常还要考虑语言障碍。即使英语成了 网络的通用语言,有些人不会理解 我们精美的书面,优雅的散文关于这些功能。但没有 这与签名有关;他们简洁地表达了一切 关于函数的重要性,除了它实际上做什么

但比这更重要的是这些签名成功的事实 非常容易思考我们的功能以及它们如何结合起来。如果 我们得到了这个功能:

map
我们已经看到的

map :: (a -> b) -> [a] -> [b] 看起来像

map(foo)

然后我们可以立即派生出函数Object的类型 注意,如果我们将a替换为Numberb map,我们满足map(foo) :: [Object] -> [Number] 的第一个参数的签名,并且 因此,通过讨论,我们将留下余数:

{{1}}

这使得使用函数有点像谚语&#34;插入 标签A插入插槽A&#34;指令。我们只能通过形状来识别 我们的功能究竟如何可以插在一起构建 更大的功能。能够做到这一点是关键特征之一 函数式编程。类型签名使其更容易 这样做。

进一步阅读

答案 2 :(得分:1)

这是一些函数式语言(最着名的是Haskell)用于其类型签名的语法。

最后一个符号表示返回类型,而所有其他符号表示参数的类型。看似奇怪的语法的原因与Haskell咖喱的事实有关;所有函数都接受1个参数并返回一个值。多功能功能由返回新功能的功能组成。任何时候你看到->,这是功能应用程序。您可以将箭头视为“黑盒子”,需要1个输入,并提供1个输出。这是我第一次启动Haskell时可视化的方式。

例如:

Number -> [a] -> [[a]]

是一个函数的签名,它接受一个数字和一般的a列表,并返回a的二维列表。请注意,在Haskell中,这将表示一个带Number的函数,并返回一个带有a列表的函数,并返回a s的二维列表。但是,您通常不需要担心currying行为。您可以调用该函数,就好像它实际上有2个参数一样。

在这种情况下,

a代表一个通用输入。我们并不关心这种类型,因为可能从未使用过各个元素。如果签名中出现一个字母而没有与类型类限制相关联(更多关于类型类别),则假设它表示一个通用参数,我们根本不关心该类型(比如在签名中添加<T>在Java中,然后使用T)。

Apply f => f (a -> b) -> f a -> f b

是一个带有函数和a的函数的签名,并返回b。它似乎是一种通用的map方法。如果列表是Apply类型类的成员,则可以认为a在这种情况下可以是列表,b是列表的修改版本。

在第二个示例中,“粗箭头”之前的部分表示类型限制。 Apply f表示在签名的其余部分中,f表示属于Apply类型类的成员的类型(类似于接口)。据推测,Apply类型类表示能够应用的类型,因此f aa(任何类型),但仅限于可应用的类型。从上下文中,我将不得不假设函数是Apply类型类的隐式成员,因为它们可以被应用,并且上面的签名在函数参数((a -> b))之前,具有{{1 }}。

这部分:

f

表示一个带(a -> b) 的函数,并将其转换为a;但在任何一种情况下,我们都不关心ba实际上是什么类型。因为它周围有括号,它代表传递的单个函数。只要您看到b之类的签名,就意味着它是高阶函数的签名。

建议阅读:

Understanding Haskell Type Signatures