elixir 规格
I’ve been working almost exclusively with Javascript/Node.js since 2016, but since February I’m in a love-hate relationship with Elixir, sometimes more hate than love (way more hate), but I had to admit that Elixir has its charm. I ended up working with Elixir because since my first contact with Node.js I wanted to have a more challenging experience with back-end. Don’t get me wrong, Node.js is challenging, but I was already familiar with Javascript and that made it easier for me. After a few interviews at SumUp, the interviewers noticed my interest in Back-End and gave me the opportunity to work with Elixir, even though I had no experience with it. And I am so grateful for that.
自2016年以来,我几乎一直只使用Javascript / Node.js进行工作,但是自2月以来,我与Elixir处于爱恨交加的关系,有时恨比爱多(远比恨),但我不得不承认Elixir有它的魅力。 我之所以结束Elixir的工作,是因为自从我第一次接触Node.js以来,我想在后端获得更具挑战性的体验。 不要误会我的意思,Node.js很有挑战性,但是我已经很熟悉Javascript,这对我来说变得更加容易。 在SumUp进行了几次采访后,访问者注意到我对Back-End的兴趣,即使我没有经验,也给了我与Elixir合作的机会。 我对此深表感谢。
So, getting back to coding, my first impression was that Elixir complicates everything, but it actually gives us some different ways to think outside the box. I’m going to talk about some features that really got me, starting with Pipe.
因此,回到编码,我的第一印象是Elixir使一切变得复杂,但实际上为我们提供了一些不同的思维方式。 我将从管道开始谈论一些真正吸引我的功能。
管 (Pipe)
The pipe operator
|>
passes the result of an expression as the first parameter of another expression.管道运算符
|>
将表达式的结果作为另一个表达式的第一个参数传递。
Before I knew pipe, I already loved its purpose. In JS, we have two ways to implement the pipe’s concept: creating a compose/pipe function or using method chaining.
在不知道管道之前,我已经爱上了它的目的。 在JS中,我们有两种方法来实现管道的概念:创建compose / pipe函数或使用方法链接。
Method Chaining:
方法链接:
When we talk about method chaining is very common to use Array Methods as an example. You can chain every Array method like the following because they all return the magic word: this. But unfortunately, we can only use this when it comes to objects or classes.
当我们谈论方法链接时,通常以数组方法为例。 您可以像下面这样链接每个Array方法,因为它们都返回了神奇的词:this。 但是不幸的是,我们只能在涉及对象或类时使用它。
const numbersList = [1, 2, 3, 4, 5, 6, 7, 8, 9]
numbersList
.map(num => num * 2)
.filter(num => num < 25)
Compose/Pipe:
撰写/管道:
In the example above we are chaining three functions. First, we get the first name, then we make it lowercase and, finally, we get only the first letter of the name. Each function passes the result to the next function. Compose is almost the same thing, but backwards.
在上面的示例中,我们链接了三个函数。 首先,我们得到名字,然后将其小写,最后,我们仅得到名字的第一个字母。 每个函数将结果传递给下一个函数。 撰写几乎是一回事,但倒退了。
const pipe = (…fns) => x => fns.reduce((v, f) => f(v), x)
pipe(
getFirstName,
makeLowercase,
getFirstLetter
)('Eve Montalvão')
// 'e'
In Elixir we have this very elegant way to do that:
在Elixir中,我们有一种非常优雅的方式来做到这一点:
"Eve Montalvão"
|> get_first_name
|> make_lowercase
|> get_first_letter
// "e"
The biggest advantage I see is to be able to pass multiple and different parameters to each function, like this:
我看到的最大优点是能够向每个函数传递多个不同的参数 ,如下所示:
"Eve"
|> get_first_name("Montalvão")
|> make_lowercase("some", "other", "param")
|> get_first_letter
// "e"
The string “Montalvão” is passed as a second parameter to get_fisrt_name
function only. There is a way to pass the same multiple parameters to all functions, but it’s not what we want, we want to be able to pass different parameters to different functions. The easiest way I managed to do this in JS is like this:
字符串 “Montalvão”仅作为第二个参数传递给get_fisrt_name
函数。 有一种方法可以将相同的多个参数传递给所有函数,但这不是我们想要的,我们希望能够将不同的参数传递给不同的函数 。 我设法在JS中执行此操作的最简单方法是这样的:
const pipe = (…fns) => x => fns.reduce((v, f) => f(v), x)
const sum = (a, b) => a + b
pipe(
num => sum(num, 3),
num => sum(num, 5),
num => sum(num, 6)
)(1)
Each function expects two parameters and since we are encapsulating each of them in an anonymous arrow function, we can pass different parameters, but this is not very classy, right? The proposal of pipe operator in Javascript is currently in stage 1, so there’s a chance that we can have it in JS in the future. I’ll drop the reference links at the end of this article.
每个函数都期望有两个参数,并且由于我们将每个参数封装在一个匿名箭头函数中,因此我们可以传递不同的参数,但这不是很经典,对吗? 用Javascript进行管道运算符的建议目前处于第1阶段,因此将来有可能在JS中使用它。 我将在本文末尾删除参考链接。
模式匹配 (Pattern Matching)
Pattern matching is a powerful feature that allows us to look for patterns in any value or data structure.
模式匹配是一项强大的功能,可让我们查找任何值或数据结构中的模式。
It can be a little confusing, but I promise it’s cool, in fact, it’s my favorite Elixir feature so far. In Elixir the =
is called Match Operator. So, when you use it, you are comparing the values on the right with the values on the left. See the following example:
可能有些混乱,但是我保证它很酷,实际上,这是到目前为止我最喜欢的Elixir功能。 在Elixir中, =
称为Match Operator 。 因此,使用它时,您正在将右侧的值与左侧的值进行比较 。 请参见以下示例:
[a, b, c] = [1, 2, 3]
IO.puts a // 1
IO.puts b // 2
IO.puts c // 3
Ok, I know there’s nothing special about it and we can do the same thing with JS’s “destructuring”, but I swear it’ll get better.
好的,我知道这没有什么特别的,我们可以使用JS的“ 解构 ”来做同样的事情,但是我发誓它会变得更好。
If we try to do the following, we’ll get an error:
如果尝试执行以下操作,则会收到错误消息:
[a, b, c, d] = [1, 2, 3]
// ** (MatchError) no match of right-hand side value: [1, 2, 3]
Want to see something weird? If we do the following, we also get an error:
想看些奇怪的东西吗? 如果执行以下操作,也会收到错误消息:
[a] = [1, 2]
What? How? Why did this happen when we definitely have a value to bind the a variable? This means that Elixir can’t match the two sides because they’re not equal in size. In the first example, we don’t have any value to bind letter d, so they’re not equal and we get an error. In the second example, we get an error because a list with 1 element could never match a list with 2 elements. This can be confusing for JS developers because when we say [a] = [1, 2]
, we are assigning the first value of the array to the variable a.
什么? 怎么样? 为什么会出现这种情况时,我们肯定是要绑定变量的值? 这意味着Elixir不能匹配两侧,因为它们的大小不相等。 在第一个示例中,我们没有任何绑定字母d的值,因此它们不相等并且会出现错误。 在第二个示例中,我们收到一个错误,因为包含1个元素的列表永远不会匹配包含2个元素的列表。 这可能会使JS开发人员感到困惑,因为当我们说[a] = [1, 2]
,我们正在将数组的第一个值分配给变量a 。
You’re probably thinking right now: “Ok, Eve, but why is this cool? I honestly think it’s terrible to receive an error for something so simple. Javascript would have undefined as the value of d and that’s good for me”. Well, that’s right, but when you understand the power of pattern matching it makes sense. Pattern matching is very useful when you need to deal with different cases in the same function. Let’s take a look at a real use case: At SumUp we have this module called FallbackController, we use it when we need to send some response to the client, through the method call:
您可能现在正在思考:“ 好吧,夏娃,但是为什么这很酷? 老实说,我认为收到如此简单的错误是很糟糕的。 Javascript将未定义为d的值,这对我有好处 ”。 是的,这是正确的,但是当您了解模式匹配的力量时,它就很有意义。 当您需要在同一函数中处理不同情况时,模式匹配非常有用 。 让我们看一个实际的用例:在SumUp中,我们有一个名为FallbackController的模块,当需要通过方法调用将某些响应发送给客户端时,可以使用它:
def call(conn, {:ok, body}) do
conn
|> put_status(:ok)
|> json("Success!")
end
At the code above we have this function called call that is pattern matching its parameters. The only way to get into this function is to send the second parameter like this:
在上面的代码中,我们有一个称为call的函数,该函数与其参数进行模式匹配。 进入此功能的唯一方法是发送第二个参数,如下所示:
{:ok, %{ prop: “value”, anotherprop: “value” }}
If we try to send the following as a second parameter, our call method will never be called.
如果我们尝试将以下内容作为第二个参数发送,则永远不会调用我们的call方法。
%{:error, %{prop: “value” }}
This happens because pattern matching can be used in function parameters as well. The value doesn’t match? The function is not called. So, let’s improve our FallbackController. We already have a method to deal with successful responses, let’s make one to deal with an error.
发生这种情况是因为模式匹配也可以在函数参数中使用 。 值不匹配? 该函数未调用。 因此,让我们改进FallbackController 。 我们已经有了一种处理成功响应的方法,让我们处理一个错误。
def call(conn, {:ok, body}) do
conn
|> put_status(:ok)
|> json("Success!")
end
def call(conn, {:error, message}) do
conn
|> put_status(:ok)
|> json(message)
end
Now when we call our method with {:ok, %{ prop: "value", anotherprop: "value" }}
, the first one is called. When we call with {: error, %{ message: "Something went wrong" }}
, the second one is called. And we can keep going until we cover all the cases we need.
现在,当我们使用{:ok, %{ prop: "value", anotherprop: "value" }}
调用方法时,将调用第一个方法。 当我们使用{: error, %{ message: "Something went wrong" }}
进行调用时,将调用第二个。 我们可以继续努力,直到涵盖了所有需要的情况。
def call(conn, {:ok, body}) do
conn
|> put_status(:ok)
|> json("Success!")
end
def call(conn, {:error, message}) do
conn
|> put_status(:ok)
|> json(message)
end
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> render(:"404")
end
I see this as a very good way to apply the single responsibility principle, since we can handle each case in its own method, instead of a messy single method with a bunch of if/elses (which is strongly not recommended in Elixir).
我认为这是应用单一责任原则的一种非常好的方法 ,因为我们可以用自己的方法来处理每种情况,而不是用一堆if / elses来处理凌乱的单一方法(在Elixir中强烈建议不要这样做)。
Another very interesting way to use pattern matching is within case, like this:
使用模式匹配的另一种非常有趣的方式是在case内,如下所示:
case {:ok, "Successful"} do
{:ok, message} ->
# do something
{:error, message} ->
# do another thing
end
The case statement works almost like our JS’s switch/case statement, but it allows us to use pattern matching instead of matching the exact value. Of course, we can also do exact matches, but I like how flexible case is. Let’s take a look at these other examples where I used ^
to let Elixir know that I want the first item of the list to be equal to our x variable. If the value is anything different than x’s value, the pattern will not match.
case语句的工作原理几乎与JS的switch / case语句一样,但是它允许我们使用模式匹配而不是精确值进行匹配。 当然,我们也可以进行精确匹配,但我喜欢大小写的灵活性。 让我们看一下其他示例,其中我使用^
使Elixir知道我希望列表的第一项等于我们的x变量。 如果该值与x的值不同,则该模式将不匹配。
x = 50
case [20, 35, 50] do
[^x, y, z] ->
# will not match because 20 is different from x
[y, ^x, z] ->
# will not match because 35 is different from x
[y, z, ^x] ->
# it's a match! x is equal to 50
_ ->
# would only match if none of the above matches
end
You can even check if a value is greater/less than another value, or maybe do more complex operations by using when.
您甚至可以检查一个值是否大于/小于另一个值,或者可以使用when进行更复杂的操作。
list = [20, 35, 50]
case list do
[x | _] when x > 25 -> x # will not match because 20 is less than 25
[x | _] when x / 10 == 2 -> x # will match because 20 / 10 is 2
list -> list
end
Another great feature is that we can mix pipe and case. Remember our pipe example? Let’s use it again, but now within case statement.
另一个伟大的功能是我们可以混合使用管道和外壳 。 还记得我们的管道示例吗? 让我们再次使用它,但现在在case 语句中。
case "Eve Montalvão"
|> get_first_name
|> make_lowercase
|> get_first_letter do
"e" -> //do something
"m" -> // do another thing
end
I know this is a very dumb example, but I believe that dumb examples are best when trying to understand a language you don’t know. Well, since we’re talking about case statements, this leads me to the last feature I’m in love with (in case you didn’t notice, I tried to make a joke here because the name of the statement is with).
我知道这是一个非常愚蠢的例子,但是我认为当试图理解一种您不懂的语言时,最好使用愚蠢的例子。 那么,既然我们正在谈论的case语句,这使我最后的功能,我爱上了 (如果你没有注意到,我试图让一个笑话,因为在这里声明的名称是 )。
用 (With)
The special form with/1 is useful when you might use a nested case/2 statement or situations that cannot cleanly be piped together.
当您可能使用嵌套的case / 2语句或无法清晰地管道连接在一起的情况时,特殊的with / 1格式很有用。
The with statement gets the case statements and takes it to another level. Imagine a usual API call where the only response that matters to us is the successful one.
with语句获取case语句并将其带到另一个级别。 想象一下通常的API调用,其中对我们而言唯一重要的响应就是成功的响应。
with {:ok, body} <- usual_api_call(url) do
# do something
else
# do something when the match doesn't happen
end
In JS, again, the closest thing we can do is “destructuring” the response object and check the value, but it’s more verbose and it would be something like this:
再次,在JS中,我们可以做的最接近的事情是“ 解构 ”响应对象并检查值,但是它比较冗长,可能是这样的:
const { statusCode } = await usualApiCall(url)
if (statusCode >= 200 && statusCode <= 299) {
// do something
}
Another use case is when you need to check for multiple case statements within different sources of data, with can do this like a charm.
另一种使用情况是,当你需要检查不同的数据来源中的多个case语句, 与能做到这一点就像一个魅力。
with {:ok, username} <- user_data,
{:ok, followers_length} <- account_data do
IO.puts "#{username} has #{followers_length} followers"
else
error -> IO.puts "Something went wrong: #{error}"
end
The with execution stops at the first non-matching line and the else block is executed, and guess what? We can also match our else, if necessary. Pretty cool, right?
with执行在第一行不匹配行处停止,else块被执行,您猜怎么着? 如果需要,我们也可以匹配其他。 很酷吧?
with {:ok, username} <- user_data,
{:ok, followers_length} <- account_data do
IO.puts "#{username} has #{followers_length} followers"
else
{:error, error} -> IO.puts "Something went wrong: #{error}"
end
If you’re a Javascript/Node.js developer looking for a new language to learn, especially if you want to learn more about functional programming, I think you should give it a try. Of course, Elixir has its bad side, for example, I’m used to having plenty of articles, a big community, clear documentation and Elixir isn’t always like that. Sometimes I struggle to understand some things because of the lack of content on the internet compared to Javascript, but it’s kind of our job to change this by creating more content.
如果您是Javascript / Node.js开发人员,正在寻找一种新的语言来学习,特别是如果您想了解有关函数式编程的更多信息,我想您应该尝试一下。 当然,Elixir也有不好的一面,例如,我习惯于有很多文章,庞大的社区,清晰的文档,Elixir并不总是那样。 有时由于与Javascript相比互联网上缺少内容,我很难理解一些事情,但是通过创建更多内容来改变这种情况是我们的工作。
And if you’re interested in learning more about pattern matching and pipe proposals for Javascript, check these references below:
并且,如果您有兴趣了解有关Java的模式匹配和管道建议的更多信息,请查看以下这些参考:
Explanation of a Pattern Matching proposal in JS
SumUp正在招聘中,如果您想成为我们团队的一员, 请单击此处 ! (SumUp is hiring, click here if you want to be a part of our team!)
And last, but not least: share with your back-end enthusiastic friends if you enjoyed this article.
最后但并非最不重要的一点:如果您喜欢这篇文章,请与您的后端热情朋友分享。
翻译自: https://medium.com/inside-sumup/a-front-ends-good-impressions-about-elixir-68f8983d8ab
elixir 规格