More data types and high-level constructs

1.Erlang is a functional programming language, and the prominent feature of Erlang is function definition. A truly functional data type is funs. They can be passed as arguments to other functions, and can be stored in data structures like tuple and record types or sent as messages to other processes. Most importantly, they can be the result of other functions, so that functions can be passed around as data and can be created dynamically in a program, not just statically defined functions, which allows you to write concise, abstract, reusable function, and it can be parameterized by a specific behavior, i.e. "wrapped" as an argument to a function, the result is that the code is not only more compact, but also easier to write, understand, and maintain.

2. List comprehension is another powerful construct with its roots in functional programming. List comprehensions allow to generate lists, combine them, and then filter the results based on a series of predicates. The result is a list of elements for which the predicate produced by the generator evaluates to true. Like funs, list comprehensions can produce compact and powerful code that increases programmer productivity.

3. Binary is another Erlang data type. Although it is not directly related to functional programming, it also has a lot of influence in Erlang. Binary is nothing more than a sequence of 1s and 0s, a piece of untyped content stored in memory, all socket and port communication is binary based, like all file related I/O. The power of using binary in Erlang lies in bit-level pattern matching, which extracts the relevant bits and bytes with very little effort and code. This makes Erlang ideal for handling protocol stacks and transport related to international protocols, mainly encoding and decoding message frames, which can be sent or received as a result of protocol stack processing with very little code.

4. Finally, data types are introduced, elements of which are often called refs, which give you unique tokens for use across processes in a distributed environment. In particular, we use reference data values ​​for comparison, many of which are message-related.

Funs and Advanced Programming

1. Fun for binding variables:

Bump=fun(Int)->Int+1 end.

This fun takes a variable as a parameter, binds it to the variable Int and increments its value by 1, you can call fun with its parameter in parentheses after it, just like calling a function, if it is assigned A variable whose name can be used:

Bump(10).

Alternatively, you can call directly:

(fun(Int)->Int+1 end)(9).

A fun is a function, but it is not uniquely identified using the module, function name and arity, but rather using the variables it is bound to or its definition.

function as parameter

1. One of the most common operations on lists is to visit each element and transform it in some way. For example, the following code multiplies all elements in a list of numbers by 2 and reverses the elements in each list:

doubleAll([])->                                                                                                       revAll([])->

[];                                                                                                                          [].

doubleAll([X|Xs])->                                                                                                revAll([X|Xs])->

[X*2|doubleAll(Xs)].                                                                                               [reverse(X)|revAll(Xs)].

The difference between these two examples is that it affects how the element X is transformed. A function can capture this transformation, given a map function whose first argument F is the function to apply to each element in the list:

map(F,[])->

[];

map(F,[X|Xs])->

[F(X)|map(F,Xs)].

2. Another common list operation is to filter elements with special properties, for example, lists of even numbers or palindromes:

evens([])->                                                                            palins([])->

[];                                                                                          [];

evens([X|Xs])->                                                                      palins([X|Xs])->

case X rem 2 ==0 of                                                       case palins(X) of

true->                                                                             true->

[X|evens(Xs)];                                                                 [X|palins(Xs)];

_->                                                                                         _->

evens(Xs)                                                                       palins(Xs)

than. than.

palin/1 is defined in module hof1 like this:

palin(X)->X==reverse(X).

The filter function wraps this "filtering" behavior as a definition, where the function P contains the property to be tested: P(X) returns true if X has this property, false otherwise:

filter(P,[])->

[];

filter(P,[X|Xs])->

case P(X) of

true->

[X|filter(P,Xs)];

_->

filter(P,Xs)

end.

Functions like P(X) that return true or false are called predicates.

Writing functions: function expressions

1. When writing a function definition: the parameters of the function must be given, pattern matching can be used to distinguish different cases, and the result is returned in the last executed expression. Besides giving the function name, a fun expression can do the same thing.

The function for parameter doubling is as follows:

fun(X)->X*2 end.

The function to add two numbers is as follows:

fun(X,Y)->X+Y end.

The function to get the header of a list (null if empty) is as follows:

fun([])->null

     ([X|_])->X

end.

2. Expressions need to be surrounded by fun...end alone, and sometimes you will see end followed by a period. This is because the dot sign indicates the end of a definition or the end of a terminal input line. It's not part of the function's own expression. The syntax used in many cases is similar to case: the keyword fun does not appear before each of the different pattern-matching cases. But keep in mind that arguments are enclosed in parentheses, like (...), even when there is only one variable as an argument.

By using function expressions, doubleAll and palins can be defined using map and filter:

doubleAll(Xs)->

map(fun(X)->X*2 end,Xs).

palins(Xs)->

filter(fun(X)->X==reverse(X) end,Xs).

In each case, it can be seen that the function expression exactly "encapsulates" the special behavior mapping or filtering property.

Function expressions can also encapsulate boundary effects, for example:

fun(X)->io:format("Element:~p~n",[X]) end.

The function will print its argument message to standard output.

fun(X)->Pid!X end.

The function sends its argument to Pid as a message.

3. A function that takes a fun as a parameter is called a higher-order function.

function as result

E.g:

sendTo(Pid)->

fun(X)->

Pid!X

end.

Case:

times(X)->

fun(Y)->X*Y end.

Times=times(3). Returns a function.

(times(3))(2): Returns 6.

Times(2): Returns 6.

Use a defined function

1. Use functions that have been defined as arguments to other functions, or as values ​​for variables. In a module M, a local function F of arity n can be expressed as fun F/n. Outside of this module, it can be marked as fun M:F/n.

2. fun foo/2 is just the syntax of the notation fun(A1,A2)->foo(A1,A2) end. The fully qualified method is fun M:F/n.

3. Remember, when passing fun to other Erlang nodes, if local and global function calls are done within fun, the module where the function definition is defined must exist in the code search path of the remote Erlang node. .

functions and variables

1. All variables introduced in a fun expression are considered new variables, so they shadow any existing variable with the same name, which means that the shadowed variable inside the fun expression can no longer be accessed, while They are still accessible when exiting the fun. All other variables can be accessed inside fun, for example:

foo()->

X=2,

Bump=fun(X)->X+1 end,

Bump(10).

funs:foo(): The result is 11 because X is overwritten.

bar()->

X=3,

Add=fun(Y)->X+Y end,

Add(10).

funs:bar(): The result is 13 because there is no X in fun.

Predefined Higher-Order Functions

1. The lists module contains a collection of higher-order functions, that is, functions that take fun as arguments and apply them to a list. These higher-order functions allow to hide recursive patterns in function calls and isolate all boundary effects and Operations on list elements in fun.

2. Higher-order function recursion patterns defined in the lists module:

all(Predicate,List): If Predicate, that is, a fun that returns a boolean value, returns true when it is applied to each element in the list and returns true, otherwise returns false.

any(Predicate,List): Returns true if any element in the predicate list returns true to the Predicate, otherwise returns false.

dropwhile(Predicate, List): Returns true if the head of the list and the recursive tail Predicate are removed.

filter(Predicate,List): Removes all elements in the list whose Predicate is false, and returns a list of the remaining elements.

foldl(Fun, Accumulator, List): Function Fun with two parameters, which are an element from List and Accumulator, fun returns the new Accumulator, which is also the return value of the call once the list has been traversed. Unlike its sister lists:foldr/3 functionality, lists:foldl/3 traverses the list in a tail-recursive manner from left to right.

map(Fun,List): Takes a Fun parameter, which applies to each element in the list. It returns a list containing the results of the fun application.

partition(Predicate,List): Divide a list into two lists, one of which contains elements for which Predicate returns true, and the other list contains elements for which Predicate returns false.

Lazy Evaluation and Lists

1. A proper or well-formed list is either an empty list or a list whose head is an element and the tail is a proper or well-formed list. Erlang's evaluation mechanism means that when a list is passed to a function, the list is fully evaluated before the function itself is executed. Other functional languages, notably Haskell, implement demand-driven (or lazy) evaluation. In the case of lazy evaluation, parameters are only evaluated within the function body when necessary; for data structures such as lists, only those parts that are needed are evaluated.

2. It is possible in Erlang to build a lazy list, to do this, you can use a list like this, with a fun at the tail, which returns a new head and a recursive fun. This will avoid generating a large list and then looping through it. Instead, you can reduce memory usage and generate subsequent values ​​only when needed.

list comprehension

1. Typical list operations are mapping—applying a function to each element in the list, and filtering—selecting elements in a list that have specific properties. Often these methods are used together, and list comprehensions provide a powerful and concise way to write list constructs.

Generic list comprehension

1. Generally speaking, a list comprehension has three components:

[Expression||Generators,Guards,Generators,Guards,...]

Builder:

The form of the generator is Pattern<-List, where Pattern is a pattern that matches the elements in the List expression, and the symbol <- can be understood as from, it is like the mathematical symbol "E", which means "an element from ".

Protection meta:

A guard is like a guard in a function definition, whose result is either true or false. The variables in the guard are those generators that appear to the left of the guard (any other variables are defined at the outer level).

expression:

The expression refers to the form of the result element. Pattern matching performs two tasks, as it does in function definitions: it allows a complex element (such as a tuple) to match member components, and it selects only those elements that match the pattern.

Binary types and deserialization

Sometimes large amounts of structured data must be transferred or stored between computers. How should the protocol be made to ensure that data can be generated and disseminated as quickly and efficiently as possible? The answer is to use all possible storage bits, and each bit in the word contains as much information as possible. Erlang's binary types provide a pattern-matching notation for manipulating binary data structures, making this type of lower-level programming simpler and more reliable, and more space-efficient than using tuples or lists.

binary type

1. A binary type is an index into a raw, untyped block of memory. Originally it was used by the Erlang runtime system to download code over the network, but it was soon used in a more general socket-based communication setup. The built-in functions provide the ability to encode, decode, and manipulate binary, and binary types can transfer large amounts of data more efficiently.

2. The built-in functions term_to_binary/1 and binary_to_term/1 encode and decode terms into binary types, ie byte sequences. The function is_binary/1 is a protection element used to test the binary type. When dealing with an octal byte stream, that is, a list of integers, you can use list_to_binary/1 and binary_to_list/1.

2. Note: It is best to use term_to_binary to encode a sequence of elements in a single binary into a single list. Encode the elements individually, concatenate the results using the function list_to_binary, then decode to give unexpected results. So to decode the elements in a list, you first need to divide it, and then decode them separately.

bit syntax

1. The bit syntax allows to think of binary as segments, which are sequences of bits but not necessarily bytes (not necessarily aligned on byte boundaries). The term bitstring is used to refer to a sequence of bits of any length, while the term binary refers to a string whose length is divisible by 8, so it can be thought of as a sequence of bytes.

2. Bins can be constructed using the following bit syntax:

Bin=<<E1,E2,...,En>>

Pattern matching can be done like this:

<<E1,E2,...,En>>=Bin

3. Examples of bit syntax:

Bin1=<<1,2,3>>: The result is <<1,2,3>>

binary_to_list(Bin1): The result is [1,2,3]

<<E,F>>=Bin1:结果是exception error:no match of right hand side value <<1,2,3>>

<<E,F,G>>=Bin1: The result is <<1,2,3>>

E: The result is 1

[B|Bs]=binary_to_list(Bin1): The result is [1,2,3]

Bin2=list_to_binary(Bs): the result is [2,3]

4. The real strength of binary is that each expression of Bin can be qualified by a size and/or type expression:

Expr:Size/Type

These restrictions allow precise control over number formats, including integers and floating-point numbers, and mean that bit programs can be read as high-level specifications of the protocol, rather than as low-level (and opaque) implementations of the protocol.

Size: The specified size is in bits, the default size of an integer is 8, and the default size of a float is 64 (8 bytes).

Type: List of type specifiers, separated by hyphens, type specifiers can be the following:

type: Valid types are integer, floating point, binary, byte, bit, and bitstring.

sign: Valid signs include signed and unsigned (the default). In the case of signed, the first bit determines the sign: 0 is positive, 1 is negative.

endian value: The endian value depends on the CPU. The endian value can be big (default), little and native. In the case of big endian, the first byte represents the least significant bit; in the case of little endian, the first byte represents the most significant bit. The endian value is only required when transferring binary between different CPU architectures. Use native if you want the endian to be determined at runtime.

Specify the unit: The number of bits used by the entry is Val*N, where N is the size of the value, the default unit of bit (bit) and bit string (bitstring) is 1; for binary and byte, it is 8.

Bitwise pattern matching

1. The same syntax can be used when matching binary, especially using size and type in pattern matching. When type is omitted, the default type is an integer, for example:

A=1: The result is 1.

Bin= <<A,17,42:16>>: The result is <<1,17,0,42>>

<<D:16,E,F/binary>>=Bin: The result is <<1,17,0,42>>

[D,E,F]: The result is [273,0,<<"*">>]

2. The way a binary is unpacked is completely different from how it is constructed, which is one of the data mechanisms for complex protocol processing, so frames can be decoded and encoded, and Erlang makes this simple:

Frame = <<1,3,0,0,1,0,0,0>> The result is <<1,3,0,0,1,0,0,0>>

<<Type,Size,Bin:Size/binary-unit:8,_/binary>>=Frame结果为<<1,3,0,0,1,0,0,0>>

Type result is 1

Size result is 3

Bin result is <<0,0,1>>

The calculation method is: because Type occupies one byte, so the value is 1, Size occupies one byte, so the value is 3, because Bin:Size occupies three bytes, so the value is <<0,0,1>>, The rest is final.

3. In binary pattern matching, there are some possible pitfalls:

The system will not understand the expression B=<<1>> because it will be read as B =<<1>>, so it is important to always separate the symbols << and = with a space.

The system will not understand the expression <<X+1:8>>, the trick is to add parentheses <<(X+1):8>> to the arithmetic expression

<<X:7/binary,Y:1/binary>> will never match, because the length of each binary sequence in the pattern match must be a multiple of 8.

You cannot use an unallocated variable as the size of a fragment, as in a function definition: foo(N,<<X:N,...>>)->... . An already defined value needs to be used here, however, if the variable is defined, it is fine to use it to specify the size of the segment.

Bit string parsing

1. The list comprehension notation [...||X->List,test,...] is a powerful way to describe lists generated from other lists by the "generate and test" method, The bit string connotation symbol performs a similar function for bit strings, for example:

<< <<bnot(X):1>>||<<X:1>> <= <<42:6>> >>结果为<<21:6>>.

2. Bit string parsing is split by <<...||...>>, and <= is used for generators (instead of <-). Most importantly, all the bit string entities are encapsulated in <<...>>, in the previous example, the variable X is the bit variable, which will be assigned to bits 101010 (in binary format of 42), among them Each of the is negated by bnot on output, resulting in the string 010101, thus obtaining the binary representation of 21.

3. The principle of bit string connotation is the same as that of lists: pattern matching syntax is used for generators (to the left of the <= sign), and the result can be described in bit syntax.

bitwise operators

Bit-level operators in Erlang can be used on integers, and the results also return integers:

bond: bitwise AND

bor: bitwise OR

bxor: bitwise exclusive or

bnot: bitwise NOT

bsl: bitwise left shift, the second parameter gives the number of bits to shift

bsr: bitwise right shift, the second parameter gives the number of bits to shift

Serialization

1. One way to serialize a data structure is to nicely print the structure as a string completely enclosed in parentheses, which can be deserialized by parsing, but this is neither an efficient storage form, nor is it an efficient deserialization mechanism.

2. When the structure is serialized, the size of the generated expression can be known. Therefore, this information can be integrated into the serialization to avoid structure analysis. Initially, this structure can be converted into a stream in which the size of the tree's expression is recorded before each subtree. E.g:

[11,8,2,cat,5,2,dog,2,enum,2,fish], where 11 is the size of the entire expression and 8 is the list [8,2,cat,5,2,dog,2 ,enum], which represents the left subtree of the top-level node.

However, there is redundant information inside the sequence format, if there is a segment representing a subtree of a node, it is enough to infer the size of the left subtree, and the right subtree is given by the rest.

quote

1. References can be created using the built-in function make_ref(), which are (almost) unique within the lifetime of a node, their values ​​are repeated only after 2 is called to the 28th power, and they are also unique to a node , so two references to two different nodes can never be the same.

2. References can be compared for equality, and calls and responses can be matched in a protocol by marking a message with an index and then returning the same index in the reply.

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326435411&siteId=291194637