- Learning Elixir
- Kenny Ballou
- 1077字
- 2021-07-23 14:47:54
Invariable variables and pattern matching
One of the most misunderstood concepts in functional programming is that of assignment. Or, said another way, assignment doesn't exist.
Let's try to dispel this misconceived idea. In iex
, we might see some code like this:
iex(1)> a = 2 2 iex(2)> a + 4 6
We may be tempted to explain the preceding code snippet with something like, "So we assign 2
to a
and then add 4
to a
giving us 6
." However, in Elixir, this is incorrect. Elixir does not define =
as an assignment operator, but rather a match operator. That is, Elixir attempts to match the left side of the =
operator to that of the right.
In step 1
, for Elixir to make the match succeed, we bind the value of 2
to the variable, a
. Then later, when we perform the addition, we are substituting 2
for a
, yielding an expression that looks like 2 + 4, which obviously equals 6
.
This is a really different way to think about what is going on internally. Take a moment to let it sink in.
Back? Ready to move forward? Good!
If all we are doing is binding, why can we do something like this:
iex(1)> a = 2 2 iex(2)> a = 3 3
This is because Elixir will rebind values to make the match succeed. Notice, however, you are not able to do this:
iex(1)> a = 2 2 iex(2)> 3 = a ** (MatchError) no match of right hand side value: 2
But we can do something like this:
iex(1)> a = 2 2 iex(2)> 2 = a 2
This is the basis of what functional languages call pattern matching and is really one of the fundamental things that makes functional programming so different and so exciting.
Since Elixir will always try to make the left-hand side match the right, we can use this to our advantage to decompose lists, extract elements, or decompose complex structures into simpler variables.
Let's take a look at decomposing lists.
We may define a list as:
iex(1)> list = [1, 2, 3] [1, 2, 3]
Then, we may attempt to match it against a, b, c
:
iex(2)> [a, b, c] = list [1, 2, 3] iex(3)> a 1 iex(4)> b 2 iex(5)> c 3
So Elixir attempts to create a match between the left side: [a, b, c]
and list
(which evaluates to [1, 2, 3]
). For Elixir to successfully match these two sides, a
, b
, and c
must be bound to the values 1
, 2
, and 3
, respectively. As we see in commands 3-5, this is indeed the case.
Similarly, literal values can be used in the match expressions, using the same list:
iex(2)> [a, 2, c] = list [1, 2, 3] iex(3)> a 1 iex(4)> c 3
Using the underscore
In many functional languages, the underscore (_
) is used to denote values we don't wish to bind to any value, or we don't care to use. Using the previous example, perhaps we don't care about the middle value of the list, but we can use the underscore to match against the 2
, or whatever it actually is:
iex(2)> [a, _, c] = list [1, 2, 3] iex(3)> a 1 iex(4)> c 3 iex(5)> _ ** (CompileError) iex:5: unbound variable _
It turns out that the underscore is a bit of a special variable. It allows us to match any expression, for example, _ = list
would match successfully, but Elixir never keeps the bound value. This is good for two reasons. Firstly, semantically speaking, when reading any Elixir code, if we see the underscore, we know we can ignore it, and, secondly, the fact that Elixir will not allow us to use it will certainly help ensure that we don't make any mistakes with its actual value.
More pattern matching
Pattern matching has all sorts of uses in Elixir. Let's consider binary data deconstruction using pattern matching. While we're at it, let's learn about IEEE-754, the standard that defines how computers handle floating-point numbers in memory.
IEEE-754
If you're familiar with the IEEE-754 standard, feel free to skip a few paragraphs ahead.
For the sake of the discussion, we will assume we are talking about 64-bit or double precision floating-point numbers. There are other representations, 32-bit/single and 128-bit/quadruple precision, but they usually only differ by the number of bits available.
The IEEE-754 standard, first introduced in 1985, defines a couple of terms we need to become familiar with for our discussion. There is the sign, the exponent, the fraction or mantissa, and the bias.
The sign is denoted by the most significant bit. The exponent is given 11 bits, and the fraction is given 52 bits. The bias is just a constant for the exponent value.
With 11 bits for the exponent, we can store numbers between -1024 to 1023, or without the sign, 0 to 2047. We use the bias to keep the sign bit out of the number; however, we don't gain more precision here, this just has to do with the arithmetic involved. For example, to store a 0 exponent, we would store 1023 or 01111111111.
The fraction or mantissa is given 52 bits. This means that we can represent the fractional bit of a floating point with 52 bits of precision. This gives us a lot of space to represent numbers. However, we can usually give ourselves a little more by assuming that the first bit is, implicitly, a leading 1.
Putting it all together, using the values of each set of bits, denoting integers, we can compute the float saved in memory:
sign(1 + mantissa/pow(2,52)) * pow(2,exp ? 1023)
Or, perhaps a better way to see how IEEE-754 works is to use Elixir to help us match the bits and rebuild it with the preceding equation:
iex(1)> <<sign::size(1), exp::size(11),mantissa::size(52)>> = <<3.14159::float>> <<64, 9, 33, 249, 240, 27, 134, 110>> iex(2)> sign 0 iex(3)> exp 1024 iex(4)> mantissa 2570632149304942 iex(5)> (1 + mantissa / :math.pow(2, 52)) * :math.pow(2, exp-1023) 3.14159
We don't include the sign
bit because we already know it's zero and the number is positive.
There are lots of reasons to learn more about IEEE-754, especially if you're computing lots of numerical results. And maybe you won't use Elixir to do those computations, but using Elixir's pattern matching on binaries is a great way to decompose and debug why using 1-tanx is a bad idea when x has values near π?4 and 5π?4.
- 演進(jìn)式架構(gòu)(原書(shū)第2版)
- CMDB分步構(gòu)建指南
- CentOS 7 Server Deployment Cookbook
- C語(yǔ)言程序設(shè)計(jì)(第2版)
- Magento 2 Theme Design(Second Edition)
- Java從入門(mén)到精通(第5版)
- Learning Python Design Patterns
- 汽車(chē)人機(jī)交互界面整合設(shè)計(jì)
- Akka入門(mén)與實(shí)踐
- Continuous Delivery and DevOps:A Quickstart Guide Second Edition
- 超好玩的Scratch 3.5少兒編程
- 實(shí)驗(yàn)編程:PsychoPy從入門(mén)到精通
- 測(cè)試工程師Python開(kāi)發(fā)實(shí)戰(zhàn)
- Learning Node.js for Mobile Application Development
- Java Web開(kāi)發(fā)系統(tǒng)項(xiàng)目教程