プログラミング言語でatomデータ型を持つ機能はどのくらい役に立ちますか?
いくつかのプログラミング言語には、ある種の定数を表すためにatomまたは記号の概念があります。私が出会った言語(LISP、Ruby、Erlang)にはいくつかの違いがありますが、一般的な概念は同じように思えます。私はプログラミング言語の設計に興味があり、atom型を持つことで実際にどのような価値がもたらされるのか疑問に思いました。 Python、Java、C#などの他の言語は、それがなくても非常にうまく機能しているようです。
私はLISPまたはRubyの実際の経験がありません(構文は知っていますが、実際のプロジェクトでも使用していません)。私はそこでのコンセプトに慣れるためにErlangを十分に使用しました。
シンボルを操作する機能がどのようにクリーンなコードにつながるかを示す短い例:(コードはScheme、LISPの方言にあります)。
(define men '(socrates plato aristotle))
(define (man? x)
(contains? men x))
(define (mortal? x)
(man? x))
;; test
> (mortal? 'socrates)
=> #t
このプログラムは、文字列または整数定数を使用して記述できます。しかし、シンボリックバージョンには特定の利点があります。シンボルは、システム内で一意であることが保証されています。これにより、2つのシンボルの比較が2つのポインターの比較と同じくらい速くなります。これは、2つの文字列を比較するよりも明らかに高速です。整数定数を使用すると、次のような意味のないコードを記述できます。
(define SOCRATES 1)
;; ...
(mortal? SOCRATES)
(mortal? -1) ;; ??
おそらく、この質問に対する詳細な答えは、本 Common LISP:A Gentle Introduction to Symbolic Computation にあります。
アトムはリテラルであり、値に独自の名前が付いた定数です。あなたが見るものはあなたが得るものであり、それ以上期待しないものです。 atom catは「猫」を意味し、それだけです。遊ぶことも、変更することも、粉々にすることもできません。猫です。対処してください。 。
原子を、名前を値として持つ定数と比較しました。以前に定数を使用したコードを使用したことがあるかもしれません。例として、目の色の値があるとします:
BLUE -> 1, BROWN -> 2, GREEN -> 3, OTHER -> 4
。定数の名前を基になる値と一致させる必要があります。アトムを使用すると、基本的な値を忘れることができます。私の目の色は、単に「青」、「茶色」、「緑」、「その他」にすることができます。これらの色は、コードのどの部分でもどこでも使用できます。基になる値が衝突することはなく、そのような定数を未定義にすることは不可能です。
http://learnyousomeerlang.com/starting-out-for-real#atoms から取得
そうは言っても、アトムは、他の言語が文字列、列挙型、または定義のいずれかを使用することを余儀なくされる場所で、コード内のデータを記述するためのより良い意味論的適合になります。それらは、同様の意図された結果のために使用するのにより安全で友好的です。
アトム(ErlangまたはPrologなど)またはシンボル(LISPまたはRubyなど)(ここではアトムとのみ呼びます)は、自然な基礎となる「ネイティブ」表現を持たないセマンティック値がある場合に非常に役立ちます。これらは、次のようなCスタイルの列挙型のスペースを取ります。
enum days { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
違いは、アトムは通常、宣言する必要がなく、心配する必要のある基底表現がないことです。 ErlangまたはPrologのatom monday
の値は、「atom monday
」であり、多かれ少なかれ何もありません。
文字列型からもアトムからと同じように使用できることは事実ですが、後者にはいくつかの利点があります。まず、アトムは一意であることが保証されているため(舞台裏では、文字列表現が何らかの形式の簡単にテストできるIDに変換されます)、同等の文字列を比較するよりもはるかに高速に比較できます。第二に、それらは不可分です。 atom monday
は、たとえばday
で終わるかどうかをテストすることはできません。これは純粋で分割できないセマンティックユニットです。概念的なオーバーロードは、より少ないです。言い換えれば、文字列表現になります。
また、Cスタイルの列挙でも同じ利点の多くを得ることができます。特に比較速度は、どちらかといえば速いです。しかし...それは整数です。そして、SATURDAY
とSUNDAY
を同じ値に変換するなどの奇妙なことを行うことができます。
enum days { SATURDAY, SUNDAY = 0, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY }
これは、異なる「シンボル」(列挙)が異なるものであると信頼できないことを意味し、したがって、コードについての推論をはるかに困難にします。あまりにも、列挙型をワイヤープロトコルを介して送信することは、それらと通常の整数を区別する方法がないため、問題があります。原子にはこの問題はありません。 atomは整数ではなく、舞台裏のようには見えません。
Cプログラマーとして、私はRubyシンボルが実際に何であるかを理解するのに問題がありました。ソースコードにシンボルがどのように実装されているかを見た後、私は悟りを開いた。
Rubyコード内には、グローバルハッシュテーブルがあり、文字列は整数にマップされています。すべてのRubyシンボルはそこに保管しました。 Rubyインタープリターは、ソースコードの解析段階で、そのハッシュテーブルを使用してすべてのシンボルを整数に変換します。その後、内部的にすべてのシンボルは整数として扱われます。これは、1つのシンボルが4バイトのメモリしか占有せず、すべての比較が非常に高速であることを意味します。
したがって、基本的には、Rubyシンボルを非常に巧妙な方法で実装された文字列として扱うことができます。 文字列のように見えますが、ほとんど整数のように機能します。
新しい文字列が作成されると、Rubyで、そのオブジェクトを保持するために新しいC構造体が割り当てられます。 2つのRuby文字列の場合、2つの異なるメモリ位置(同じ文字列を含む場合があります)への2つのポインタがあります。ただし、シンボルはすぐにCint型に変換されます。したがって、2つのシンボルを2つの異なるRubyオブジェクトとして区別する方法はありません。これは実装の副作用です。コーディングするときはこれを覚えておいてください。それだけです。
LISPではsymbolとatomは2つの異なる無関係な概念です。
通常、LISPではATOMは特定のデータ型ではありません。これはNOTCONSの省略形です。
(defun atom (item)
(not (consp item)))
また、タイプATOMはタイプと同じです(CONSではありません)。
Consセルではないものはすべて、CommonLISPではatomです。
SYMBOLは特定のデータ型です。
シンボルは、名前とアイデンティティを持つオブジェクトです。シンボルはパッケージに挿入できます。シンボルには、値、関数、およびプロパティリストを含めることができます。
CL-USER 49 > (describe 'FOO)
FOO is a SYMBOL
NAME "FOO"
VALUE #<unbound value>
FUNCTION #<unbound function>
PLIST NIL
PACKAGE #<The COMMON-LISP-USER package, 91/256 internal, 0/4 external>
LISPソースコードでは、変数、関数、クラスなどの識別子はシンボルとして記述されます。 LISPのS式がリーダーによって読み取られる場合、それらが不明な場合(現在のパッケージで利用可能)、または既存のシンボルを再利用する場合(現在のパッケージで利用可能である場合)、新しいシンボルを作成します。LISPリーダーが読み取りを行う場合のようなリスト
(snow snow)
次に、2つの短所セルのリストを作成します。各consセルのCARは、同じシンボルを指します雪。 LISPメモリにはそのシンボルが1つだけあります。
また、シンボルのplist(プロパティリスト)には、シンボルの追加のメタ情報を格納できることに注意してください。これは、作成者、ソースの場所などである可能性があります。ユーザーは、自分のプログラムでこの機能を使用することもできます。
Scheme(およびLISPファミリーの他のメンバー)では、シンボルは単に有用であるだけでなく、不可欠です。
これらの言語の興味深い特性は、それらが 同像性 であるということです。 Schemeプログラムまたは式は、それ自体が有効なSchemeデータ構造として表すことができます。
例はこれをより明確にするかもしれません(Gaucheスキームを使用して):
> (define x 3)
x
> (define expr '(+ x 1))
expr
> expr
(+ x 1)
> (eval expr #t)
4
ここで、exprは単なるリストであり、記号+、記号x、および数字1。このリストを他のリストと同じように操作したり、渡したりすることができます。ただし、評価することもできます。その場合、コードとして解釈されます。
これが機能するためには、Schemeが記号と文字列リテラルを区別できる必要があります。上記の例では、xはシンボルです。意味を変えずに文字列リテラルに置き換えることはできません。リストをとると '(print x)、ここでxは記号を評価します。これは、 '(print "x")以外の意味です。ここで、 "x"は文字列です。
ちなみに、Schemeデータ構造を使用してScheme式を表現する機能は、単なる仕掛けではありません。式をデータ構造として読み取り、何らかの方法で変換することが、マクロの基本です。
pythonにはアトムやシンボルに類似したものはありません。Pythonでアトムのように動作するオブジェクトを作成することは難しくありません。オブジェクトを作成するだけです。単純な空のオブジェクトです。例:
>>> red = object()
>>> blue = object()
>>> c = blue
>>> c == red
False
>>> c == blue
True
>>>
多田! Pythonのアトム!私はいつもこのトリックを使います。実際には、それ以上に進むことができます。これらのオブジェクトにタイプを与えることができます:
>>> class Colour:
... pass
...
>>> red = Colour()
>>> blue = Colour()
>>> c = blue
>>> c == red
False
>>> c == blue
True
>>>
これで、色にタイプがあるので、次のようなことができます。
>>> type(red) == Colour
True
>>>
つまり、これは、プロパティリストの場合と同様に、機能的にはlispyシンボルとほぼ同等です。
一部の言語では、連想配列リテラルには記号のように動作するキーがあります。
Python [1]では、辞書。
d = dict(foo=1, bar=2)
Perl [2]では、ハッシュ。
my %h = (foo => 1, bar => 2);
JavaScript [3]では、オブジェクト。
var o = {foo: 1, bar: 2};
このような場合、foo
とbar
は記号のようなものです。つまり、引用符で囲まれていない不変の文字列です。
[1]証明:
x = dict(a=1)
y = dict(a=2)
(k1,) = x.keys()
(k2,) = y.keys()
assert id(k1) == id(k2)
[2]これは完全に真実ではありません。
my %x = (a=>1);
my %y = (a=>2);
my ($k1) = keys %x;
my ($k2) = keys %y;
die unless \$k1 == \$k2; # dies
[1] JSONでは、キーを引用符で囲む必要があるため、この構文は許可されていません。変数のメモリを読み取る方法がわからないため、それらがシンボルであることを証明する方法がわかりません。
原子は、eとは対照的に、一意で統合されていることが保証されています。たとえば、浮動小数点定数値。エンコード中の不正確さのために異なる場合があり、それらをネットワーク経由で送信し、反対側でデコードして、浮動小数点に変換し直します。使用しているインタープリターのバージョンに関係なく、atomは常に同じ「値」を持ち、一意であることが保証されます。
Erlang VMは、すべてのモジュールで定義されているすべてのアトムをグローバル アトムテーブル に格納します。
Erlangにはブールデータ型はありません があります。代わりに、アトムtrue
およびfalse
がブール値を示すために使用されます。これにより、このような厄介なことをすることができなくなります。
#define TRUE FALSE //Happy debugging suckers
Erlangでは、アトムをファイルに保存し、読み戻し、リモートのErlangVM間でネットワークを介して渡すことができます。
例として、いくつかの用語をファイルに保存してから、読み返します。これはErlangソースファイルlib_misc.erl
(または今私たちにとって最も興味深い部分)です:
-module(lib_misc).
-export([unconsult/2, consult/1]).
unconsult(File, L) ->
{ok, S} = file:open(File, write),
lists:foreach(fun(X) -> io:format(S, "~p.~n",[X]) end, L),
file:close(S).
consult(File) ->
case file:open(File, read) of
{ok, S} ->
Val = consult1(S),
file:close(S),
{ok, Val};
{error, Why} ->
{error, Why}
end.
consult1(S) ->
case io:read(S, '') of
{ok, Term} -> [Term|consult1(S)];
eof -> [];
Error -> Error
end.
次に、このモジュールをコンパイルして、いくつかの用語をファイルに保存します。
1> c(lib_misc).
{ok,lib_misc}
2> lib_misc:unconsult("./erlang.terms", [42, "moo", erlang_atom]).
ok
3>
ファイルerlang.terms
には、次の内容が含まれています。
42.
"moo".
erlang_atom.
それを読み返してみましょう:
3> {ok, [_, _, SomeAtom]} = lib_misc:consult("./erlang.terms").
{ok,[42,"moo",erlang_atom]}
4> is_atom(SomeAtom).
true
5>
データがファイルから正常に読み取られ、変数SomeAtom
が実際にatom erlang_atom
を保持していることがわかります。
lib_misc.erl
の内容は、The PragmaticBookshelfが発行したJoeArmstrongの「ProgrammingErlang:Software for aConcurrentWorld」から抜粋したものです。残りのソースコードは ここ です。
他の言語(Cなど)の同様の概念で私が抱えている問題は、次のように簡単に表現できます。
#define RED 1
#define BLUE 2
#define BIG 1
#define SMALL 2
または
enum colors { RED, BLUE };
enum sizes { BIG, SMALL };
次のような問題が発生します。
if (RED == BIG)
printf("True");
if (BLUE == 2)
printf("True");
どちらも本当に意味がありません。アトムは、上記の欠点なしに同様の問題を解決します。
Rubyでは、シンボルはハッシュのキーとして使用されることが多いため、Ruby 1.9では、ハッシュを作成するための省略形も導入されています。以前は次のように記述していました。
{:color => :blue, :age => 32}
これで、次のように書くことができます。
{color: :blue, age: 32}
基本的に、これらは文字列と整数の間の何かです。ソースコードでは、文字列に似ていますが、かなりの違いがあります。同じ2つの文字列は実際には異なるインスタンスですが、同じシンボルは常に同じインスタンスです。
> 'foo'.object_id
# => 82447904
> 'foo'.object_id
# => 82432826
> :foo.object_id
# => 276648
> :foo.object_id
# => 276648
これは、パフォーマンスとメモリ消費の両方に影響を及ぼします。また、それらは不変です。割り当てられたときに一度変更されることを意図していません。
議論の余地のある経験則は、出力用ではないすべての文字列に文字列の代わりに記号を使用することです。
おそらく無関係に見えますが、ほとんどのコードハイライトエディタは、他のコードとは異なる色でシンボルを色付けし、視覚的に区別します。
アトムはオープン列挙型のようなもので、可能な値は無限であり、事前に何も宣言する必要はありません。これが、実際に通常使用される方法です。
たとえば、アーランでは、プロセスは少数のメッセージタイプの1つを受信することを期待しているため、メッセージにアトムのラベルを付けるのが最も便利です。他のほとんどの言語では、メッセージタイプに列挙型を使用します。つまり、新しいタイプのメッセージを送信するときはいつでも、それを宣言に追加する必要があります。
また、列挙型とは異なり、atom値のセットを組み合わせることができます。Erlangプロセスのステータスを監視したいとし、標準のステータス監視ツールがあるとします。プロセスを拡張して、に応答することができます。ステータスメッセージプロトコルおよび他のメッセージタイプ。列挙型を使用して、この問題をどのように解決しますか?
enum my_messages {
MSG_1,
MSG_2,
MSG_3
};
enum status_messages {
STATUS_HEARTBEAT,
STATUS_LOAD
};
問題は、MSG_1が0であり、STATUS_HEARTBEATも0であるということです。タイプ0のメッセージを受け取ったとき、それは何ですか?原子の場合、私はこの問題を抱えていません。
アトム/シンボルは、一定時間比較された単なる文字列ではありません:)。
アトムはIDを使用するため、高速な同等性テストを提供します。列挙型または整数と比較して、セマンティクスが優れており(とにかく抽象的なシンボリック値を数値で表すのはなぜですか?)、列挙型のような固定値のセットに制限されません。
妥協点は、システムが一意性を維持するために既存のすべてのインスタンスを認識する必要があるため、リテラル文字列よりも作成にコストがかかることです。これは主にコンパイラに時間がかかりますが、O(一意のアトムの数)のメモリがかかります。