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作者正在使用的语法(或者可能是从别处合作,但是进一步修改)只是为了他们的库。
答案 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
此功能接受val
,Number
和nbrs
列表,
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
。该
它接受的第一个函数需要Number
和String
和
返回(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
但显然还有更多的可能性。我们不能简单地列出
商场。为了解决这个问题,类型签名不仅涉及具体问题
String
,Object
和map
等类,但也包括
泛型类的表示。
我们如何描述// map :: (a -> b) -> [a] -> [b]
?这很简单。第一个参数是
一个函数,它接受一种类型的元素,并返回一个元素
第二种类型。 (这两种类型不一定要有所不同。)
第二个参数是该输入类型的元素列表
功能。它返回该输出类型的元素列表
功能
我们可以这样描述它:
a
我们使用通用占位符,而不是具体类型 低字符字母代表任意类型。
将这些与具体类型区分开来很容易。那些是
完整的词语,按照惯例是大写的。通用类型变量
只是b
,c
,k
等。偶尔,如果有充分理由,
我们可能会使用字母表后面的一封信,如果它有帮助的话
通用可能代表什么类型的感觉(想想v
和
key
的{{1}}和value
的{{1}}或n
,但我们大多只是使用
这些是从字母表开头的。
请注意,一旦在签名中使用泛型类型变量,它就会出现
表示对同一变量的所有使用都固定的值。我们
不能在签名的一部分中使用b
,然后在其他地方重复使用
除非两者在整个签名中必须属于同一类型。
此外,如果签名中的两种类型必须相同,那么我们就有了
为他们使用相同的变量。
但没有什么可说的,有时两个不同的变量是不可能的
指向相同的类型。 map(n => n * n, [1, 2, 3]); //=> [1, 4, 9]
是
(Number → Number) → [Number] → [Number]
,所以如果我们匹配的话
(a → b) → [a] → [b]
,然后a
和b
都指向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 Candy
或Box 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'
别名Name
和Url
显示在" =
"的左侧。其
等效值显示在右侧。
如上所述,这也可用于创建更多的简单别名
复杂的类型。 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
上的Strings
,Dates
上的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的相同形状的结构
其中的价值观。我们可能会映射Dictionary
,Wrapper
,平原
Functor
只包含一个值或许多其他类型。
可以映射的东西的概念由a捕获
其他语言和FantasyLand从抽象借用的代数类型
数学,称为Functor
。 map
只是一种类型
包含一个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
没有详述它的作用或使用方法Monoid
或
Ord
,我们至少可以看到需要提供哪种类型
使此功能正常运行。
[^ maximum-note]:此最大功能存在问题;它 将在空列表中失败。试图解决这个问题需要我们 太远了。
[^ strong-types]:有一些很好的工具可以解决这个问题 Javascript的缺点,包括语言技术,如 Ramda的姐妹项目Sanctuary,Javascript的扩展 更强类型,例如flow和TypeScript,以及 更强类型的语言,编译为Javascript,如 ClojureScript,Elm和PureScript。
有时而不是试图找到最通用的版本 签名,列出几个相关签名更直接 分别。这些包含在Ramda源代码中,作为两个单独的 JSDoc标签,最终作为文档中的两个不同的行。这个 我们如何在自己的代码中编写一个:
flip
显然,如果我们选择,我们可以做两个以上的签名。但是呢 请注意,这不应该太常见。目标是写签名 通用性足以捕获我们的用法,而不是如此抽象 它们实际上模糊了函数的用法。如果我们可以这样做 单一签名,我们可能应该。如果需要两个,那就这样吧。 但如果我们有很长的签名列表,那么我们可能会错过一个 共同抽象。
从中移植此样式签名涉及多个问题 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可以替换为通用类型名称,例如b
或
Object
。这种变化可能随时发生。
有几种方法可以选择代表普通的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
由两个参数化
通用变量a
和f
。我们知道存在约束
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
替换为Number
和b
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 a
是a
(任何类型),但仅限于可应用的类型。从上下文中,我将不得不假设函数是Apply
类型类的隐式成员,因为它们可以被应用,并且上面的签名在函数参数((a -> b)
)之前,具有{{1 }}。
这部分:
f
表示一个带(a -> b)
的函数,并将其转换为a
;但在任何一种情况下,我们都不关心b
或a
实际上是什么类型。因为它周围有括号,它代表传递的单个函数。只要您看到b
之类的签名,就意味着它是高阶函数的签名。
建议阅读: