この資料は主に、Erlang-eで書き始めたばかりの人、または書き始めたい人を対象としています。 しかし、私は言語のこの側面を完全にカバーしようとしたので、この文章がより上級の読者に役立つことを願っています。
最初の材料は3つの部分に分割する必要がありました。この基本的な言語では、基本的な種類を作成する方法と、各種類の消費リソースを検討します。
エントリー
そもそも、カルマを高め、この記事をハブに掲載する機会を与えてくれたGoogleのロシア語Erlangメーリングリストの参加者に深く感謝したいと思います。
プレゼンテーション中に、Erlangのコマンドシェル(シェル)からサンプルが提供されます。 したがって、作業の簡単な原則を学ぶ必要があります。 シェル内の各コマンドはコンマで区切られています。 同時に、それはまったく問題ではなく、セットは1行または複数行で作成されます。
1> X=1, Y = 2,
1> Z = 3,
1> S=4.
4
入力の完了と実行の開始へのポインタはピリオドです。 この場合、シェルは最後のコマンドで返された値を表示します。 上記の例では、変数Sの値が返されます。開始されたすべての変数の値は記憶され、Erlangで開始された変数の値を再定義できないため、オーバーライドしようとするとエラーになります。
2> Z=2.
** exception error: no match of right hand side value 2
したがって、現在の作業セッションでシェルが変数の値を「忘れる」ようにする必要がある場合は、f()関数を使用できます。 引数なしで呼び出され、初期化されたすべての変数を削除します。 引数として変数名を指定すると、それだけが削除されます(変数のリストを転送することはできません):
3> f(Z).
ok
4> X = 4.
** exception error: no match of right hand side value 4
5> f().
ok
6> X = 4. %,
4
終了するには、単にhalt()を入力するか、Crtl + Gユーザーインターフェイスを呼び出してqを入力します(hコマンドはヘルプを表示します)。 シェルでデジタルデータを出力する場合、それらは10進数に変換されます。
提示されている資料は、現在最新のバージョン5.6.5に関連しています。 文字列のエンコードには、 ISO-8859-1 (Latin-1)エンコードが使用されます。 したがって、すべての数値文字コードはこのエンコードから取得されます。 エンコードの前半(コード0〜127)はUS-ASCIIコードに対応しているため、ラテンアルファベットに問題はありません。
内部表現ではLatin-1を仮想マシンの「外部」で使用するという開発者の声明にもかかわらず、これはしばしば完全に明白ではありません。 これは、Erlangがコードの形式で文字を送受信するために発生します。 ロケールが端末に設定されている場合、コードは設定されたコードページに基づいて解釈され、可能であれば印刷された文字で表示されます。 SSHセッションの例を次に示します。
# setenv | grep LANG
LANG=
# erl
Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.6.5 (abort with ^G)
1> [255].
"\377"
2> halt().
# setenv LANG ru_RU.CP1251
# erl
Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.6.5 (abort with ^G)
1> [255].
""
さらに、LANG環境変数に示されていることはまったく問題ではありません。主なことは、インストールする必要があるということです。
1.基本タイプ
言語には多くの基本的なタイプはありません。 この番号(整数または浮動小数点)、アトム、バイナリデータ、ビット文字列、関数オブジェクト(JavaScriptに類似)、ポート識別子、プロセス識別子(システムのアーランではなく、プロセスのアーラン)、タプル、リスト。 いくつかの疑似タイプがあります:write、boolean、string。 すべてのデータ型(必ずしも基本ではない)は用語と呼ばれます。
1.1番号
2種類の数値がサポートされています。 浮動小数点数( Float )および整数( Integer )。 一般に受け入れられている数字の表記形式に加えて、2つの特定の表記法があります。
- $ char
char文字のASCIIコード(ロケールに依存)。 - ベース # 値
基数表記の整数値 。基数は2〜36の範囲です。
例:
1> 42.
42
2> $A.
65
3> $\ .
10
4> 2#101.
5
5> 16#1f.
31
6> 2.3.
2.3
7> 2.3e3.
2.3e3
8> 2.3e-3.
0.0023
9>$.
255
出力の数値は10進数に変換されることを思い出してください。
メモリの消費と制限。 全体で1つのマシンワードを使用します。これは、32ビットプロセッサでは32バイト、64ビットプロセッサでは8バイトです。 大きな整数の場合1 ... Nマシンワード。 アーキテクチャに応じて、浮動小数点数はそれぞれ4および3マシンワードを占有します。
1.2リスト
リスト( リスト )を使用すると、データを1つの構造にグループ化できます。 リストは角括弧を使用して作成され、リスト項目はコンマで区切られます。 リスト項目は任意のタイプにすることができます。 リストの最初の要素はheadと呼ばれ、残りはtailと呼ばれます。
1> Var = 5.
5
2> [1,45, atom, Var,"string", $Z, 2#101].
[1,45,atom,5,"string",90,5]
3>List = [1, 9.2, 3], % 1, [9.2, 3]
3>List.
[1,9.2,3]
リストのサイズは、リスト内の要素の数に等しくなります。 上記の例では、 List変数の値はリストであり、リストのサイズは3です。
リストは動的な構造です。 リスト項目を追加および削除できます。 仮想マシンの内部では、リストは単一リンクリストの構造であり、特定の処理機能を課しますが、その詳細は以下のとおりです。
メモリの消費と制限。 各リストアイテムは、1マシンワード(アーキテクチャに応じて4または8バイト)+アイテムに格納されているデータのサイズを占有します。 したがって、32ビットアーキテクチャでは、 List変数の値は(1 + 1)+(1 + 4)+(1 + 1)= 9ワードまたは36バイトを占有します。
1.3文字列
実際、Erlangには行( String )はありません。 これは、より便利な形式で整数のリストを作成できる構文糖衣です。 このリストの各項目は、対応する文字のASCIIコードです。
1> "Surprise".
"Surprise"
2> [83,117,114,112,114,105,115,101].
"Surprise"
3> "".
""
4> [$,$,$,$,$,$].
""
5> [$, $, $, $, $, $, 1].
[241,242,240,238,234,224,1]
したがって、仮想マシンは、アイテムコードを印刷文字に変換できるリストを見ると、その前に行があることを理解し、記号形式で表示します。 他の多くの言語とは異なり、Erlangの文字列は二重引用符を使用して作成され、単一ではありません。 これは、アトムが単一引用符を使用して作成されるためです。 制御文字列は文字列内で許可されます(以下を参照)。
メモリの消費と制限。 なぜなら 行は整数のリストであり、各文字はリストの1つの要素であり、文字は8または16バイト(2マシンワード)かかります。
1.4アトム
Atomは単なるリテラルです。 他の言語の定数のようなデジタル値と関連付けることはできません。 アトムによって返される値は、アトム自体です。 アトムは小文字で始まり、数字、ラテン文字、アンダースコア_、または@犬で構成する必要があります。 この場合、単一引用符から省略することができます。 他の文字がある場合、単一引用符を使用してフレーム化する必要があります。 二重引用符は、これには適していません。 行を囲みます。
例:
hello
phone_number
'Monday'
'phone number'
文字列および引用符で囲まれたアトムでは、次のエスケープシーケンスを使用できます。
シーケンス
| 説明
|
\ b
| リターン(バックスペース)
|
\ d
| 削除(削除)
|
\ e
| エスケープ(エスケープ)
|
\ f
| ページフィード(フォームフィード)
|
\
| 改行
|
\ r
| キャリッジリターン
|
\ s
| スペース
|
\ t
| 水平タブ(タブ)
|
\ v
| 垂直タブ
|
\ XYZ、\ YZ、\ Z
| 8進文字コード
|
\ ^ a ... \ ^ z
\ ^ A ... \ ^ Z | Ctrl + A ... Ctrl + Z
|
\ '
| 一重引用符
|
\ "
| 二重引用符
|
\\
| バックスラッシュ
|
引用符で囲まれていないアトムの名前を予約語にすることはできません。 これらの単語は次のとおりです。
and and Band begin bnot bor bsl bsr bxor case catch cond div end fun not let or orelse query receive rem try when when xor。
メモリの消費と制限。 宣言された各アトムは一意であり、そのシンボリック表現は、 アトムテーブルと呼ばれる仮想マシンの内部構造に格納されます 。 アトムは4バイトまたは8バイト(1マシンワード)を取り、そのシンボリック表現を含むアトムテーブルの要素への単なるリンクです。 ガベージコレクションは、Atomテーブルをクリーンアップしません。 テーブル自体もメモリ空間を占有します。 255文字のアトムを使用できます。合計で1,048,576個のアトムを使用できます。 したがって、255文字のアトムは255 * 2 + 1 * Nマシンワードを占有します。Nはプログラム内のアトムへの参照の数です。
1.5タプル
タプルはリストのようなもので、要素のセットで構成されています。 また、要素の数と同じサイズですが、リストとは異なり、サイズは固定されています。 タプルは中括弧を使用して作成され、その要素は任意のデータ型にすることができ、タプルはネストできます。
1> {1,2、2#110、n、$ r、[1、5]、 "abc"}。 {1,2,6、n、114、[1,5]、 "abc"} 2>男= {男、 2> {名前、「Alexey」}、 2> {高さ、{メートル、1.86}}、 2> {年齢、27}}。 {男性、{名前、「Alexey」}、{高さ、{メートル、1.86}}、{年齢、27}} 3> Man2 = {man、 3> {名前、「Ivan」}、 3> {高さ、{meter、1.80}}、 3> {年齢、25}}。 4> [Man、Man2]。 [{man、{name、 "Alexey"}、{height、{meter、1.86}}、{age、27}}、 {man、{name、 "Ivan"}、{height、{meter、1.8}}、{age、25}}] 5> {20、100}。 {20,100}。
タプルは、構造に特定のデータを含めるだけでなく、それらを記述することもできるという点で便利です。 これは、固定タプルと同様に、テンプレートに非常に効果的に適用することを可能にします。 最初の要素でタプルを作成するときは、タプルの本質を記述するアトムを記述することをお勧めします。 RDBMSとの類似性を引き出す場合、リストはテーブルであり、テーブルの各行はリストの要素であり、この要素にあるタプルは対応する列の特定のレコードです。
メモリの消費と制限。 タプルは2マシンワード+データ自体を保存するのに必要なサイズを取ります。 たとえば、5行目のタプルは、32アーキテクチャで(2 + 1)+(2 + 1)= 6マシンワードまたは24バイトを占有します。 タプルの要素の最大数は67 108 863です。
1.6記録
レコードは実際には構文糖の別の例であり、内部表現のタプルとして保存されます。 コンパイル段階のレコードはタプルに変換されるため、シェルでレコードを直接使用することはできません。 ただし、rd()関数を使用してレコード構造を宣言できます(1行目)。 レコード宣言は常に2つの要素で構成されます。 最初の要素は、 レコード名と呼ばれるアトムでなければなりません。 2番目は常にタプルで、空の場合もあります。その要素はfield_name - field_valueのペアであり、フィールド名はアトムであり、値は有効なタイプ(レコード11行目を含む)でなければなりません。
レコード(行2)に基づいてタプルを作成するための演算子は、ラティス#の後にレコード名とフィールド値を持つタプルが続き、場合によっては空ですが、レコード記述で宣言されていないフィールド名を持つことはありません。
1> rd(person, {name = "", phone = [], address}).
person
2> #person{}.
#person{name = [],phone = [],address = undefined}
3> #person{phone=[1,2,3], name="Joi", address="Earth"}.
#person{name = "Joi",phone = [1,2,3],address = "Earth"}
タプルに対するレコードの利点は、要素の処理がフィールドの名前によって実行されることです(名前はアトムでなければならず、レコードの説明、行1で宣言する必要があります)。 したがって、割り当ての順序は重要ではありません。最初の要素に名前、電話、または住所があることを覚えておく必要はありません。 フィールドの順序を変更し、新しいフィールドを追加できます。 また、対応するフィールドが指定されていない場合、デフォルト値を割り当てることができます。
4> rd(person, {name="Smit", phone}).
person
5> P = #person{}.
#person{name = "Smit",phone = undefined}
6> J = #person{phone = [1,2,3], name = "Joi"}.
#person{name = "Joi",phone = [1,2,3]}
7> P#person.name.
"Smit"
8> J#person.name.
"Joi
9> W = J#person{name="Will"}.
#person{name = "Will",phone = [1,2,3]}
エントリを作成するときに(行4)、デフォルト値が定義されていない場合(フィールド電話)、その値は未定義のアトムと等しくなります。 7行目と8行目に説明されている構文を使用して、レコードを使用して作成された変数の値にアクセスできます。変数の値を新しい変数にコピーできます(9行目)。 さらに、フィールドの値が定義されていない場合、完全なコピーが取得され、定義されている場合、対応するフィールドは新しい変数で再定義されます。 これらの操作はすべて、レコードの定義にも古い変数のフィールドの値にも影響しません。
個人的には、これはクラスの説明とインスタンス化を非常に思い出させますが、これはタプル変数を格納する単なる方法であることをもう一度強調しています。
10> rd(name, {first = "Robert", last = "Ericsson"}).
name
11> rd(person, {name = #name{}, phone}).
person
12> P = #person{name = #name{first="Robert",last="Virding"}, phone=123}.
#person{name = #name{first = "Robert",last = "Virding"},
phone = 123}
13> First = (P#person.name)#name.first.
"Robert"
上記の例は、ネストされたエントリと内部要素のアクセス構文を示しています。
1.7バイナリデータとビット文字列
バイナリタイプ( Binaries )とビット文字列( ビット文字列 )の両方により、バイナリコードを直接操作できます。 バイナリタイプとビット文字列の違いは、バイナリデータは整数バイトのみで構成される必要があることです。 それらのビット数は8の倍数です。 ビット文字列を使用すると、ビットレベルでデータを操作できます。 基本的に、バイナリ型はビット文字列の特殊なケースであり、そのビット数は8の倍数です。 構造を記述してデータを作成し、テンプレートでこのタイプを使用できます。 バイナリデータは次の構造で記述されます。
<<E1, E2, ... En>>
このような構造の別の要素は、 セグメントと呼ばれます 。 セグメントは、バイナリデータの論理構造を表し、任意の数のビット/バイトで構成されます。 これは、テンプレートで使用すると非常に強力で便利なツールになります(このようなアプリケーションの例については、第3部で説明します)。
1> <<20, $W, 50:8, "abc">>.
<<20,87,50, "abc" >>
2> <<400>>.
<<144>>
3> <<400:16>>.
<<1,144>>
4> Var = 30.
30
5> <<(Var + 30), (20+5)>>.
<<60,25>>
理由を理解するために、2行目にバイナリデータを作成した結果、予想される400ではなく、144(つまり、出力時にすべてのデジタルデータを10進数に変換するシェルを忘れていないため、10010000)が得られましたセグメント記述のビット構文を検討してください。
Ei =値| 値:サイズ| 値/ TypeSpecifierList | 値:サイズ/ TypeSpecifierList
完全なセグメント記述フォームは、 値 、 サイズ 、および修飾子 ( TypeSpecifierList )で構成されます。 さらに、サイズと指定子はオプションであり、指定しない場合はデフォルト値を使用します。
コンストラクターの値には、数値(整数または浮動小数点)、ビット文字列、または文字列を使用できます。これらは、実際には整数のリストです。 ただし、同時に、セグメントの値を偶数のリストにすることはできません。 コンストラクタ内では、文字列はリストではなく整数への文字ごとの変換のための構文糖衣です。 つまり エントリ<<“ abc” >>は<< [$ a、$ b、$ c] >>ではなく、<< $ a、$ b、$ c >>の構文糖です。
テンプレート内では、値はリテラルまたは未定義の変数にすることができます。 ネストされたテンプレートは許可されません。 式はValueでも使用できますが、この場合、セグメントを括弧で囲む必要があります(5行目)。
サイズはセグメントのサイズを単位( Unit 、それらについては少し低い)で決定し、数値でなければなりません。 Sizeのデフォルト値はタイプ( Type 、以下を参照) Valueに依存しますが、明示的に設定することもできます。 整数の場合、これは8、64の浮動小数点数、バイナリはバイト数に対応し、ビット文字列はビット数に対応します。 ビット単位の合計セグメントサイズは、 Size * Unitとして計算できます。
テンプレートで使用する場合、 Size値は明示的に設定する必要があり(7行目)、残りのデータが含まれるため、最後のセグメントのみに設定することはできません(必要な文字数の長さを指定せずに、開始文字から行末までの行を読み取るのと同様です)。
6> Bin = <<30>>.
<<30>>
7> <<X:2, Y:3, Z/bits>> = Bin,
8> Z. % 3 , 110
<<6:3>>
指定子 ( TypeSpecifierList )は、ハイフンで区切られ、ランダムな順序で書き込まれた絞り込みオプションのリストで構成されます (読みやすくするために、ユニットを最後に記述することをお勧めします)。
-
Type = integer | float | binary | bytes | bitstring | bits
Valueのタイプを示します。 バイトは、バイナリの短い形式の書き込みであり、ビット文字列のビットです。 デフォルト値は整数です。
-
Signedness = signed | unsigned
整数に符号があるか、符号なしの値であるかを示します。 タイプ全体に対してのみ意味があります。 デフォルト値は符号なし(つまり、正の符号なし整数)です。
-
Endianness = big | little | native
バイトオーダー 。 1バイトは0〜255の整数の範囲をエンコードできるため、大きな数値の場合は2バイト以上が必要です。 たとえば、2バイトでエンコードされた数値400は00000001 10010000のようになり、最初のバイトが最初(最古から最下位まで)と見なされます。 これは、ネットワークバイトオーダー(ビッグエンディアン)です。 下位バイトが最初に来ると信じられているとき、彼らはIntelバイトオーダー(リトルエンディアン)について話します。 値nativeは、仮想マシンが実行されている中央処理装置に「ネイティブ」であるモードに応じて、ブート時にバイト順序が設定されることを意味します。 順序は数字に対してのみ意味があります。 デフォルト値は大きいです。
-
Unit = unit:IntegerLiteral
単位 1〜256の範囲の数値。 Sizeとともに、セグメントサイズをビット単位で一意に決定し、明示的にSizeを指定しないと指定できません。 デフォルト値は、数値とビット文字列の場合は1、バイナリタイプの場合は8です。
したがって、2行目の例はより明確になるはずです。 Erlangでは、異なるサイズを明示的に指定しない限り、デフォルトのバイナリデータコンストラクターは1バイトに等しいセグメントを作成します。 したがって、行2には、
<<400:8/integer-unsigned-big-unit:1>>
という形式のレコードが含まれます。これは、仮想マシンによって最後の1バイトに切り捨てられます。 ネットワークバイトシーケンスでは、値が10010000の最後のバイト、つまり 144から10進値。 シーケンスを少し指定すると、最後は00000001バイトになります。 1から10進値。 セグメントが値をエンコードできる場合、切り捨ては発生しません。
9> <<400:16>>.
<<1,144>>
10> <<400:16/little>>.
<<144,1>>
11> <<400:8/unit:2>>.
<<1,144>>
ビット構文を使用すると、同じデータをさまざまな方法で記述できます(行9と11は同じ2バイト構造を記述しています)。
メモリの消費と制限。 3 ... 6ビット+直接データ自体。 32番目のアーキテクチャでは、536,870,911バイトの操作が可能です。64ビットシステムでは、2,305,843,009,213,693,951バイトが可能です。 大きな構造を処理するには、処理関数を自分で作成する必要があります。
ご注意 エントリ
B=<<1>>
は、
B =<<1>>
として解釈され
B =<<1>>
(つまり、Bは<1 >>以下です)。 正しいフォームにはスペースが含まれます:
B = <<1>>
。
1.8リンク
参照はmake_ref / 0によって作成される用語であり、一意と見なすことができます。 このようなデータ構造に主キーとして使用できます。
メモリの消費と制限。 32ビットアーキテクチャでは、現在のローカルノードではリンクごとに5マシンワード、リモートノードでは7ワードが必要です。 それぞれ64ワード、4ワード、6ワード。 さらに、リンクはノードテーブルに関連付けられており、 RAMも消費します。
1.9ブール
ブール型は擬似型です 実際、これらはtrueとfalseの 2つの原子にすぎません。
1.10関数オブジェクト
楽しい (Pattern11、...、Pattern1N)[GuardSeq1の場合]-> Body1; ...; (PatternK1、...、PatternKN)[GuardSeqKの場合]-> ボディク 終わり
関数宣言はfunキーワードで始まり、 endキーワードで終わります。 パラメーターのセットは、括弧内のコンマを介して渡されます。各パラメーターはテンプレートです。 入力パラメーターがパターンに一致する場合、一連の命令が記号->から;に実行されます。 つまり 本質的に、入力引数は入力フィルターとして機能します。 複数のパターンが一致しない場合、エラーメッセージが生成されます。
1> Fun = fun({centimetre, X}) -> {meter, X/100} end.
#Fun<erl_eval.6.13229925>
2> Fun(10).
** exception error: no function clause matching
erl_eval:'-inside-an-interpreted-fun-'(10)
3> Fun({centimetre, 10}).
{meter,0.1}
ただし、関数の定義が異なる場合、2行目のエラーは発生しません。
4> f()。 わかった 5> Fun = fun({centimetre、X})-> 5> {meter、X / 100}; 5>(X)-> 5> X / 100 5>終了。 #Fun <erl_eval.6.13229925> 6>楽しい(10)。 0.1
したがって、入力引数は、関数で宣言されたものと同じ型でなければなりません。 whenキーワードの後およびbefore- >には、結果がtrueまたはfalseである式を含めることができます。 式がtrueを返す場合、関数の本体が実行されます。すべてのチェック中に関数の本体が実行されなかった場合(つまり、関数が何も返さなかった場合)、エラーが生成されます(行12)。関数内の変数はローカルです。
7> F = fun(X)when X <0-> 7> X + 1; 7>(X)X> 0の場合-> 7> X-1; 7>(0)-> 0 7>終了。 #Fun <erl_eval.6.13229925> 8> F(5)。 4 9> F(-5)。 -4 10> F(0)。 0 11> X = fun(Y)Y> 0-> Y + 1 endの場合。 #Fun <erl_eval.6.13229925> 12> X(-5)。 **例外エラー:erl_evalに一致する関数句はありません: '-inside-an-interpreted-fun-'(-5) 13> X(5)。 6
関数はネストでき、結果は内部の外部関数によって返されます:メモリの消費と制限。この関数は、9〜13マシンワード+環境のサイズを取ります。さらに、関数は、メモリも占有する関数のテーブルに関連付けられています。
14> Adder = fun(X) -> fun(Y) -> X + Y end end.
#Fun<erl_eval.6.72228031>
15> Add6 = Adder(6).
#Fun<erl_eval.6.72228031>
16> Add6(10).
16
1.11プロセスID
Erlangプロセスの作成時に、プロセス識別子(Pid)は関数を返し
spawn/1,2,3,4, spawn_link/1,2,3,4 and spawn_opt/4
ます。プロセスと対話するための便利なメカニズムは、デジタル識別子ではなく名前でアクセスすることです。したがって、Erlangでは、シンボリック名をPidに関連付けて、その名前を使用してプロセスにメッセージを送信できます。メモリ消費と制限。このタイプは、ローカルノードに1マシンワード、リモートノードに5マシンワードを取ります。さらに、この関数は、メモリも占有するノードテーブルに関連付けられます。
1> spawn(m, f, []).
<0.51.0>
1.12ポートID
ポート識別子(ポート識別子)関数が返す
open_port/2
あなたがポートを作成します。ポートは、Erlangプロセスと外部の世界との相互作用の基本的なメカニズムです。外部プログラムと通信するためのバイト指向のインターフェースを提供します。ポートを作成したプロセスは、ポートの所有者または接続されたプロセスと呼ばれます。ポートに。ポートを通過するすべてのデータは、ポートの所有者も通過します。プロセスが完了すると、ポート自体と外部プログラムも終了するはずです。外部プログラムは、標準入出力からデータを送受信する別のオペレーティングシステムプロセスである必要があります。すべてのErlangプロセスはポートを介してデータを送信できますが、ポートの所有者のみがポートを介してデータを受信できます。ポート識別子およびプロセス識別子には、記号名を登録できます。
メモリ消費と制限。このタイプは、ローカルノードに1マシンワード、リモートノードに5マシンワードを取ります。さらに、関数はノードテーブルとポートテーブルに関連付けられており、これもメモリを消費します。
PSこれについては、基本型の説明で最初の部分を終了します。その瞬間まで、私が継続を取り上げるまで、まだ時間がない人には、ドキュメント「Getting Started with Erlang」の章の翻訳である「Getting Started with Erlang」に精通することを強くお勧めします。