我在哪里可以找到用于解释函数式编程的符号的解释/概要,特别是Ramda.js? [英] Where can I find an explanation/summary of symbols used to explain functional programming, specifically Ramda.js?
问题描述
JavaScript函数式编程库Ramda.js的API文档包含符号缩写,但没有提供理解这些缩写的图例。是否有一个地方(网站,文章,cheatsheet等),我可以去破译这些?
来自Ramda.js API文档的一些例子: p>
数字 - >数字 - > Number
Apply f => f(a - > b) - > f a - > f b
数字 - > [a] - > [[a]]
(* ... - > a) - > [*] - > a(b,b,...,m)→v)}→> ((a,b,...,m) - > {k:v})
可过滤的f => (a - >布尔) - > 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(ta)
我目前能够理解Ramda.js试图做的事情,而且我经常可以做出有教育意义的猜测,就像上面所说的那样。但是,如果我更好地理解这些符号/陈述,我确信我会更容易理解。我想了解各个组件的含义(例如特定字母,关键字,不同箭头类型,标点符号等)。我也想知道如何读取这些行。
我还没有成功搜索这个或搜索StackExchange。我已经使用了拉姆达,函数式编程,符号,缩写,速记等各种组合。我也不完全确定我是否正在寻找(A)普遍使用的缩写(或者甚至只是普通的编程),或者(B)拉姆达作者正在使用的专用语法(或者可能从其他地方合作,但进一步修改),仅仅是为了他们的图书馆。
来自Ramda Wiki :
(Part 1/2 - 对于单个SO回答太长!)
类型签名
(或什么是那些有趣的箭头?)
查看Ramda的 over
函数,我们看到
的第一件事是看起来像是两条线e:
Lens s a - > (a - > a) - > s - > s
Lens s a = Functor f => (a - > f a) - > s - > fs
对于从其他FP语言进入Ramda的人来说,这些看起来可能是熟悉的
,但对于JavaScript开发人员来说,他们可以是纯粹的gobbledy-gook。
在这里,我们描述如何在Ramda文档中读取这些内容,以及如何将
用于您自己的代码。
最后,一次我们了解如何 这些工作,我们将调查 许多 ML 受影响的语言(包括 Haskell )使用 我们不会尝试创建一个正式的描述,而只是捕捉到 这里有一个简单的函数, 我们在箭头之前和之后放置的是 在这个函数中,函数接受两个参数,一个位置 - 在Javascript中,与Haskell不同,函数可以接受多个 当然,对于需要三个参数,我们只是在括号内扩展 对于任何更大的有限参数列表也是如此。 注意ES6样式箭头 通过将参数名称替换为它们的类型,使用 我们使用所有相同类型的值列表。如果我们 该函数的输入是<$ c $的 List C>数秒。有关于精确我们所列举的意思的单独 这些列表可以是当然也有函数返回值: 我们不应该意识到我们可以合并这些: 这个函数接受一个 这很重要意识到这是所有签名告诉我们。 还有一个非常重要的类型,我们没有真正讨论过。 事实上,我们已经看到了我们如何表示函数。每个签名 这里函数 使用Ramda,我们可能不会像这样编写一个 相反,在拉姆达,人们很可能会写一个咖喱 这个curried函数可以通过提供 在Ramda中,在我们调用函数之前,模糊不会被解决。当 curried函数的签名总是像这样,由$ 由此组成。我没有一个真正的功能指向这里。但是我们 这只比一点点复杂拉姆达功能是现实的。 [^咖喱-desc]:对于来自其他语言的人,拉姆达的 如果您使用过 从这个,我们想要将所有以下类型的签名应用于 但显然还有更多的可能性。我们不能简单地列出所有的 我们如何描述 这是我们如何描述它的: 除了具体的类型,我们使用泛型占位符,单个 很容易将这些与具体类型区分开来。这些是b $ b全文,按惯例是大写。通用类型变量 请注意,一旦在签名中使用泛型类型变量, 但是没有什么可以说两个不同的变量有时不能指向相同的类型。 有些类型更复杂。我们可以很容易想象一个代表 这是我们如何指定 不一定只有一个类型参数。我们可能有一个 没有像这样的许多声明在拉姆达本身,但我们 有时候我们的类型会失去控制,因为它们内在的复杂性或者它们太通用了,所以很难用它们来工作 这个想法很简单。如果我们有一个参数化类型 别名 如前所述,这也可以用来创建一个简单的别名到更多 稍后我们将尝试分解该复数值,但现在, (单独回答。) 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? Some examples from the Ramda.js API documentation: 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. 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. From the Ramda Wiki: (Part 1 / 2 -- too long for a single SO answer!) (or "What are all those funny arrows about?") Looking at the documentation for Ramda's 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. 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. Here we have a simple function, 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. In this one, the function accepts two parameters, a position -- which is
a 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:
Of course for a function that takes three parameters, we just extend the
comma-separated list inside the parentheses: And so too for any larger finite list of parameters. It might be instructive to note the parallel between the ES6-style arrow
function definition and these type declarations. The function is defined
by By replacing the argument names with their types, the body with the
type of value it returns and the fat arrow, "
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: The input to this function is a List of Such lists can be the return values from a function, too, of course: And we should not be surprised to realize that we can combine these: This function accepts a 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 [^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. Here the function Using Ramda, we would probably not write a Instead, in Ramda, one would most likely write a curried 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
In Ramda, the ambiguity is not resolved until we call the function. When
we call The signatures of curried functions always look like this, a sequence of
Types separated by ' 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 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]: For people coming from other languages, Ramda's
currying is perhaps somewhat different than you're used to: If If you've worked with From this, we would want to apply all the following type signatures to
map: 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 How would we describe This is how we could describe it: Instead of the concrete types, we use generic placeholders, single
lower-character letters to stand for arbitrary types. 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 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 But there is nothing to say that two different variables can't sometimes
point to the same types. Some types are more complex. We can easily imagine a type representing a
collection of similar items, let's call it a This is how we specify a There does not have to be just a single type parameter. We might have a
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. 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. The idea is simple. If we had a parameterized type The aliases 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 We'll try to break down that complex value a little later, but for now,
it should be clear enough that whatever (Part 2 in a separate answer.) 这篇关于我在哪里可以找到用于解释函数式编程的符号的解释/概要,特别是Ramda.js?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
为什么人们会希望他们。
$ b
标准方法描述其函数的签名。由于
函数式编程在Javascript中变得越来越常见,这种
签名的风格正在慢慢地变得几乎标准化。我们借用并适应了拉姆达的
Haskell版本。
的精髓这些签名通过例子。
//长度::字符串 - > Number
const length = word => word.length;
长度('abcde'); // => 5
length
,它接受一个类型为
String
的单词,并返回字符串中的字符数,即
数
。该函数上面的注释是一个签名行。它使用函数的名称,然后是分隔符 ::
开始
,然后是函数的
实际描述。应该很清楚该描述的
语法是什么。提供函数的输入,
然后是箭头,然后是输出。在源代码中,您通常会看到上面写有
的箭头, - >
,并且会显示为→
输出
文档。他们的意思完全一样。
参数的类型,而不是它们的名。在这个级别的描述中,我们真正的
所说的是,这是一个函数,它接受一个字符串并返回一个
数字。
// charAt ::(Number,String) - >字符串
const charAt =(pos,word)=> word.charAt(POS); charAt(9,'mississippi'); // => 'p'
a Number
- 和一个单词 - 它是一个 String
- 并返回一个
单字符字符串
或空字符串字符串
。
参数。为了显示一个需要两个参数的函数,我们使用逗号分隔
两个输入参数,并将该组包含在括号中:
(Number,String)
。与许多语言一样,Javascript函数
参数是位置的,所以顺序很重要。 (字符串,数字)
具有完全不同的含义。
逗号分隔列表:
// foundAtPos ::(Number ,String,String) - >布尔
const foundAtPos =(pos,char,word)=> word.charAt(pos)=== char;
foundAtPos(6,'s','mississippi'); // => true
函数定义与这些类型声明之间的平行可能是有益的。该函数被
定义为
$ p $ (pos,word)=> word.charAt(POS);
类型 - >
,以及一个瘦身的, - > ;
,我们得到签名:
//(Number,String) - >字符串
值列表
需要一个函数来添加列表中的所有数字,我们可以使用:
// addAll: :[数字] - > Number
const addAll = nbrs => nbrs.reduce((acc,val)=> acc + val,0);
addAll([8,6,7,5,3,0,9]); // => 38
讨论,但现在,我们可以将
看作基本上就好像它们是阵列一样。要描述一个给定类型的List
,我们用方括号 []
包装这个类型名称。 字符串
的清单
应该是 [字符串]
,布尔
s将是
[布尔值]
,的列表列表数字
s会是 [[数字]]
。
// findWords :: String - > [String]
const findWords = sentence => sentence.split(/ \s + /);
findWords('她在海边出售贝壳');
// => [She,sells,seashells,by,the,seashore]
// addToAll ::(Number, [数字]) - > [数字]
const addToAll =(val,nbrs)=> nbrs.map(nbr => nbr + val);
addToAll(10,[2,3,5,7]); // => [12,13,15,17]
Number
, val
以及数字
s,
nbrs
,并返回一个新列表 Number
s。
没有办法区分这个函数,仅仅通过签名来区分
和其他碰巧接受 Number
的函数和
Number
s并返回一个 Number
s的列表。[^ theorems]
[^定理]:那么我们可以收集其他信息,以
的形式出现在
函数
函数式编程全是关于函数的;我们将函数作为
参数传递,并接收函数作为其他
函数的返回值。我们也需要表示这些。
行记录了一个特定的功能。我们在
中重复使用了上面的技巧,用于签名中使用的高阶函数。
// applyCalculation ::((数字 - >数字),[数字]) - > [数字]
const applyCalculation =(calc,nbrs)=> nbrs.map(nbr => calc(nbr));
applyCalculation(n => 3 * n + 1,[1,2,3,4]); // => [4,7,10,13]
calc
由
(数字→数字)
描述
就像我们的顶级函数签名一样,只是用
括号包裹把它作为一个单独的单元进行合理分组我们可以使用从另一个函数返回的函数完成
同样的事情:
// makeTaxCalculator :: Number - > ; (Number - > Number)
const makeTaxCalculator = rate => base =>
Math.round(100 * base + base * rate)/ 100;
const afterSalesTax = makeTaxCalculator(6.35); //税率:6.35%
售后税(152.83); // => 162.53
makeTaxCalculator
接受税率,表示作为百分比(类型
Number
),并返回一个新函数,它自己接受一个 Number
并返回一个 Number
。同样,我们描述由
(Number→Number)返回的函数
,这使得整个函数
Number→(Number→Number)
的签名。
Currying
makeTaxCalculator
完全是
。拉姆达,我们可能会在这里获得
的优势。[^ curry-desc]
calculateTax
函数可以像 makeTaxCalculator
一样使用,如果这是
您想要的,但可以也可用于单次传递:
// calculateTax :: Number - >麻木呃 - > Number
const calculateTax = R.curry((rate,base)=>
Math.round(100 * base + base * rate)/ 100);
const afterSalesTax = calculateTax(6.35); //税率:6.35%
售后税(152.83); // => 162.53
// OR
calculateTax(8.875,49.95); // => 54.38
front和back两个参数来使用一个值,或者只提供一个值并且获得
返回正在寻找第二个函数的函数。为此,我们使用
数字→数字→数字
。在Haskell中,模糊性很容易被解析为
:箭头绑定到右边,所有函数都使用
单个参数,尽管手边有一些句法花招可以使
感觉为尽管你可以用多个参数调用它们。
时,我们调用 calculateTax(6.35)
,因为我们选择不提供
第二个参数,所以我们返回最后的数字→数字
签名的一部分。当我们调用 calculateTax(8.875,49.95)
时,我们提供了
前两个 Number
参数,并且因此只返回最后的
Number
。
→
分隔的一系列
类型。因为这些类型中的一些可能
本身就是函数,所以可能会有一些带圆括号的子结构
,它们本身就有箭头。这是完全可以接受的:
// someFunc ::((Boolean,Number) - > String) - > (对象 - >布尔) - >
//(Object - > Number) - >对象 - >字符串
可以从它的类型签名中学到一些关于这种功能的公平信息。它
接受三个函数和一个 Object
并返回一个 String
。它自己接受的
first函数需要一个布尔值
和一个数字
,
返回一个字符串
。请注意,这里没有描述这是一个curried
函数(或者它被写为(布尔→数字→
。)第二个函数参数接受一个
字符串) Object
并返回
a Boolean
,第三个函数接受一个 Object
并返回一个 Number
。
我们通常不具有四个参数的函数,并且我们当然不会有
接受三个函数参数。所以如果这一点很清楚的话,那么我们就可以很好地理解拉姆达必须抛出的
美元。
currying可能与以前不同:如果 f ::
/ p>
(A,B,C)→ (a)(b)(c)== g(c)(c)(c)和(c $ c $) 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]); // => [
$ / pre
map:
// map ::(String - > String) - > [字符串] - > [String]
// map ::(String - > Number) - > [字符串] - > [Number]
// map ::(Number - > Number) - > [数量] - > [数字]
// map ::(Number - > Boolean) - > [数量] - > [布尔]
。为了解决这个问题,输入签名不仅要处理具体的
类,如 Number
, String
,并且 Object
,而且还包含泛型类的
表示形式。
地图
?这很简单。第一个参数是
一个函数,它接受一种类型的元素,并返回一个元素
的第二种类型。 (这两种类型不一定必须不同。)
第二个参数是该
函数的输入类型的元素列表。它返回该
函数的输出类型的元素列表。
// map ::(a - > b) - > [a] - > [b]
lower-字符代表任意类型。
只是 a
, b
, c
等。偶尔,如果有强有力的理由,
,我们可能会使用字母表后面的一个字母,如果它有助于使
感知泛型可能代表什么类型的类型(认为 k
和
v
用于键
和 value
或 n
为一个数字),但大多数情况下我们只是使用
这些从字母开始。
表示对于同一变量的所有用途都固定的值。我们
不能在签名的一部分中使用 b
,然后在其他地方重复使用
,除非它们在整个过程中必须是相同的类型签名。
此外,如果签名中的两个类型必须相同,那么我们有
来为它们使用相同的变量。
map(n => n * n,[1,2,3]); // => [1,4,9]
是
(数字→数字)→[数字]→[数字]
,所以如果我们'重新匹配
(a→b)→[a]→[b]
,然后 a
和 b
指向数字
。
这不是问题。我们仍然有两个不同的类型变量,因为
会有不同的情况。
参数化类型
类似物品集合的类型,我们称之为 Box
。但没有实例是
任意 Box
;每个人只能容纳一种物品。当我们
讨论 Box
时,我们总是需要指定一个 Box
的东西。
// makeBox :: Number - >数字 - >数字 - > [a] - > Box a
const makeBox = curry((height,width,depth,items)=> / * ... * /);
// addItem :: a - >方框a - > Box a
const addItem = curry((item,box)=> / * ... * /);
Box
参数化由未知类型 a
:
Box a
。这可以用在我们需要某个类型的地方,作为一个参数或
函数的返回值。当然,我们也可以使用
来定义类型,也可以是 Box Candy
或 Box Rock
。 (虽然这
是合法的,但我们目前在拉姆达实际上并没有这样做,也许
我们根本不想被指责为像一盒石头一样笨手笨脚。) p>
Dictionary
类型,该类型根据键
的类型和它使用的值的类型进行参数化。这可以写成字典k
。这也表明我们可能会使用单个
v
字母的地方,这些字母不是来自字母表的最初字母。
可能会发现自己在自定义代码中经常使用这些东西。
最大的用途是支持类型类,所以我们应该描述
类。
类型别名
。 Haskell允许类型别名来简化对这些元素的理解
。拉姆达也借用了这个概念,尽管它很少使用
。
用户字符串
,其中
字符串是为了表示一个名字,我们希望更具体的关于类型的
当生成
URL时,我们可以创建一个类型别名:
// toUrl ::用户名u =>网址 - > u - > Url
// Name = String
// Url = String
const toUrl = curry((base,user)=> base +
user.name.toLowerCase()。替换(/ \W / g,' - '));
toUrl('http://example.com/users/',{name:'Fred Flintstone',年龄:24});
// => 'http://example.com/users/fred-flintstone'
名称
和 Url
显示在 =
左侧。他们的
等价值出现在右边。
复杂类型。 Ramda中的一些函数使用 Lens
es,并且这些类型的
类型通过使用类型别名进行简化:
// Lens sa = Functor f => (a - > f a) - > s - > fs
它应该足够清楚,无论> Lens sa
表示,它下面的
只是复杂表达式的别名, Functor
。
f⇒(a→fa)→s→fs
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)
Type Signatures
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
Named Types
// length :: String -> Number
const length = word => word.length;
length('abcde'); //=> 5
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.// charAt :: (Number, String) -> String
const charAt = (pos, word) => word.charAt(pos); charAt(9, 'mississippi'); //=> 'p'
Number
-- and a word -- which is a String
-- and it returns a
single-character String
or the empty String
.(Number, String)
. As with many languages, Javascript function
parameters are positional, so the order matters. (String, Number)
has
an entirely different meaning.// foundAtPos :: (Number, String, String) -> Boolean
const foundAtPos = (pos, char, word) => word.charAt(pos) === char;
foundAtPos(6, 's', 'mississippi'); //=> true
(pos, word) => word.charAt(pos);
=>
", with a skinny one,
"->
", we get the signature:// (Number, String) -> String
Lists of Values
// addAll :: [Number] -> Number
const addAll = nbrs => nbrs.reduce((acc, val) => acc + val, 0);
addAll([8, 6, 7, 5, 3, 0, 9]); //=> 38
Number
s. 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 String
s would be [String]
, a list of Boolean
s would be
[Boolean]
, a List of Lists of Number
s would be [[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
, and a list of Number
s,
nbrs
, and returns a new list of Number
s.Number
and a list of
Number
s and return a list of Number
s.[^theorems]Functions
// 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
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
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)
.Currying
makeTaxCalculator
exactly
like that. Currying is central to Ramda, and we would probably take
advantage of it here.[^curry-desc]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
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.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. 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
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
.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)
.Type Variables
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]
// map :: (String -> String) -> [String] -> [String]
// map :: (String -> Number) -> [String] -> [Number]
// map :: (Number -> Number) -> [Number] -> [Number]
// map :: (Number -> Boolean) -> [Number] -> [Boolean]
Number
, String
, and Object
, but also with
representations of generic classes.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]
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
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]
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.Parameterized Types
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) => /* ... */);
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
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.Type Aliases
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'
Name
and Url
appear to the left of an "=
". Their
equivalent values appear to the right.Lens
es, 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
represents,
underneath it is just an alias for the complicated expression, Functor
f ⇒ (a → f a) → s → f s
.