Lisp 및 Erlang Atoms, Ruby 및 구성표 기호. 얼마나 유용합니까?
프로그래밍 언어에서 원자 데이터 유형을 갖는 기능이 얼마나 유용합니까?
일부 프로그래밍 언어에는 일종의 상수를 나타내는 원자 또는 기호 개념이 있습니다. 내가 접한 언어 (Lisp, Ruby, Erlang)에는 몇 가지 차이점이 있지만 일반적인 개념은 동일한 것 같습니다. 프로그래밍 언어 디자인에 관심이 있고 원자 유형이 실제 생활에서 어떤 가치를 제공하는지 궁금합니다. Python, Java, C #과 같은 다른 언어는 그것 없이는 꽤 잘 작동하는 것 같습니다.
Lisp 또는 Ruby에 대한 실제 경험이 없습니다 (구문을 알고 있지만 실제 프로젝트에서는 사용하지 않았습니다). 나는 거기에 개념에 익숙해 질만큼 Erlang을 사용했다.
기호를 조작하는 기능이 어떻게 더 깨끗한 코드로 이어지는지를 보여주는 간단한 예 : (코드는 Lisp의 방언 인 Scheme에 있습니다).
(define men '(socrates plato aristotle))
(define (man? x)
(contains? men x))
(define (mortal? x)
(man? x))
;; test
> (mortal? 'socrates)
=> #t
문자열이나 정수 상수를 사용하여이 프로그램을 작성할 수 있습니다. 그러나 상징적 버전에는 몇 가지 장점이 있습니다. 기호는 시스템에서 고유함을 보장합니다. 이렇게하면 두 포인터를 비교하는 것만 큼 빠르게 두 기호를 비교할 수 있습니다. 이것은 두 문자열을 비교하는 것보다 분명히 빠릅니다. 정수 상수를 사용하면 사람들이 다음과 같은 의미없는 코드를 작성할 수 있습니다.
(define SOCRATES 1)
;; ...
(mortal? SOCRATES)
(mortal? -1) ;; ??
아마도이 질문에 대한 자세한 답변은 Common Lisp : A Gentle Introduction to Symbolic Computation 책에서 찾을 수 있습니다 .
원자는 리터럴이며 값에 대한 자체 이름을 가진 상수입니다. 당신이 보는 것은 당신이 얻는 것이고 더 많은 것을 기대하지 않습니다. 원자 고양이는 "고양이"를 의미하며 그게 전부입니다. 당신은 그것을 가지고 놀 수도없고, 바꿀 수도없고, 산산조각 낼 수도 없습니다. 고양이 야. 받아 들여.
나는 원자를 값으로 이름을 가진 상수와 비교했습니다. 이전에 상수를 사용한 코드로 작업했을 수 있습니다. 예를 들어 눈 색깔에 대한 값이 있다고 가정 해 보겠습니다
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 }
차이점은 원자는 일반적으로 선언 할 필요가 없으며 걱정할 기본 표현이 없다는 것입니다. 원자 monday
얼랑 또는 프롤로그 "는 원자의 값을 갖는다 monday
"아무것도 더 이하.
아톰에서와 마찬가지로 문자열 유형에서도 동일한 용도로 사용할 수 있다는 것은 사실이지만 후자에는 몇 가지 이점이 있습니다. 첫째, 원자가 고유하다는 것이 보장되기 때문에 (배후에서 해당 문자열 표현이 쉽게 테스트 할 수있는 ID로 변환 됨) 동등한 문자열을 비교하는 것보다 비교하는 것이 훨씬 빠릅니다. 둘째, 그들은 나눌 수 없습니다. 원자 monday
는 day
예를 들어 끝이 있는지 확인하기 위해 테스트 할 수 없습니다 . 순수하고 분할 할 수없는 의미 단위입니다. 즉, 문자열 표현에서보다 개념적 오버로딩이 적습니다.
C 스타일 열거로도 동일한 이점을 얻을 수 있습니다. 특히 비교 속도는 더 빠릅니다. 하지만 ... 그것은 정수입니다. 그리고 동일한 값을 가지고 SATURDAY
있고 SUNDAY
번역하는 것과 같은 이상한 일을 할 수 있습니다 .
enum days { SATURDAY, SUNDAY = 0, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY }
즉, 다른 "기호"(열거 형)가 다른 것으로 믿을 수 없으므로 코드에 대한 추론이 훨씬 더 어려워집니다. 또한 유선 프로토콜을 통해 열거 형을 보내는 것은 문제가됩니다. 그것들과 일반 정수를 구분할 방법이 없기 때문입니다. 원자에는이 문제가 없습니다. 원자는 정수가 아니며 뒤에서 하나처럼 보이지 않습니다.
C 프로그래머로서 저는 Ruby 기호가 실제로 무엇인지 이해하는 데 문제가있었습니다. 나는 소스 코드에서 심볼이 어떻게 구현되는지보고 깨달았다.
Ruby 코드에는 정수로 매핑 된 문자열 인 전역 해시 테이블이 있습니다. 모든 루비 기호가 거기에 유지됩니다. Ruby 인터프리터는 소스 코드 구문 분석 단계에서 해당 해시 테이블을 사용하여 모든 기호를 정수로 변환합니다. 그런 다음 내부적으로 모든 기호는 정수로 처리됩니다. 이것은 하나의 심볼이 4 바이트의 메모리 만 차지하고 모든 비교가 매우 빠르다는 것을 의미합니다.
따라서 기본적으로 Ruby 심볼을 매우 영리한 방식으로 구현 된 문자열로 취급 할 수 있습니다. 그들은 문자열처럼 보이지만 거의 정수처럼 작동합니다.
새 문자열이 생성되면 Ruby에서 해당 객체를 유지하기 위해 새 C 구조가 할당됩니다. 두 개의 Ruby 문자열에 대해 두 개의 다른 메모리 위치 (동일한 문자열을 포함 할 수 있음)에 대한 두 개의 포인터가 있습니다. 그러나 심볼은 즉시 C int 유형으로 변환됩니다. 따라서 두 기호를 두 개의 다른 Ruby 객체로 구분할 방법이 없습니다. 이것은 구현 의 부작용 입니다. 코딩 할 때이 점을 명심하십시오.
Lisp에서 기호 와 원자 는 서로 다른 두 가지 관련되지 않은 개념입니다.
일반적으로 Lisp에서 ATOM은 특정 데이터 유형이 아닙니다. NOT CONS에 대한 짧은 손입니다.
(defun atom (item)
(not (consp item)))
또한 ATOM 유형은 유형 (NOT CONS)과 동일합니다.
단점 셀이 아닌 것은 Common Lisp의 원자입니다.
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-expression을 읽는 경우, 알 수없는 경우 (현재 패키지에서 사용 가능) 새 기호를 생성하거나 기존 기호를 재사용합니다 (현재 패키지에서 사용 가능한 경우). Lisp 판독기가 같은 목록
(snow snow)
그런 다음 두 개의 단점 셀 목록을 만듭니다. 각 단점 셀의 CAR은 동일한 기호 snow를 가리 킵니다 . Lisp 메모리에는 하나의 기호 만 있습니다.
또한 심볼의 plist (속성 목록)는 심볼에 대한 추가 메타 정보를 저장할 수 있습니다. 이것은 작성자, 소스 위치 등일 수 있습니다. 사용자는 또한 자신의 프로그램에서이 기능을 사용할 수 있습니다.
Scheme (및 Lisp 제품군의 다른 구성원)에서 기호는 유용 할뿐만 아니라 필수적입니다.
이러한 언어의 흥미로운 특성은 동음 이의어라는 것 입니다. Scheme 프로그램 또는 표현식 자체는 유효한 Scheme 데이터 구조로 표현 될 수 있습니다.
예를 들어 Gauche Scheme을 사용하여 더 명확하게 만들 수 있습니다.
> (define x 3)
x
> (define expr '(+ x 1))
expr
> expr
(+ x 1)
> (eval expr #t)
4
여기서 expr 은 기호 + , 기호 x 및 숫자 1 로 구성된 목록 입니다. 이 목록을 다른 것과 같이 조작하고 전달하는 등의 작업을 수행 할 수 있습니다. 그러나 평가할 수도 있습니다.이 경우 코드로 해석됩니다.
이것이 작동하려면 Scheme은 기호와 문자열 리터럴을 구별 할 수 있어야합니다. 위의 예에서 x 는 기호입니다. 의미를 변경하지 않고는 문자열 리터럴로 바꿀 수 없습니다. 우리는 목록에 걸릴 경우 '(인쇄 x)를 , x는 것보다 다른 수단 뭔가가 있다는 상징을, 그것을 평가 ( "X"인쇄)' , 여기서 "X"문자열입니다.
Scheme 데이터 구조를 사용하여 Scheme 표현식을 표현하는 기능은 단순한 속임수가 아닙니다. 표현식을 데이터 구조로 읽고 어떤 방식 으로든 변환하는 것이 매크로의 기초입니다.
일부 언어에서 연관 배열 리터럴에는 기호처럼 작동하는 키가 있습니다.
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에서는 키를 인용해야하므로이 구문은 허용되지 않습니다. 나는 변수의 메모리를 읽는 방법을 모르기 때문에 그것들이 상징이라는 것을 증명하는 방법을 모릅니다.
예를 들어, 인코딩, 유선을 통해 전송, 다른 쪽에서 디코딩 및 부동 소수점으로 다시 변환하는 동안 부정확성으로 인해 다를 수있는 부동 소수점 상수 값과는 대조적으로 원자는 고유하고 통합적입니다. . 어떤 버전의 인터프리터를 사용하든 관계없이 atom이 항상 동일한 "값"을 가지며 고유한지 확인합니다.
Erlang VM은 전역 원자 테이블의 모든 모듈에 정의 된 모든 원자를 저장 합니다 .
Erlang에는 부울 데이터 유형 이 없습니다 . 대신 원자 true
및 false
부울 값을 나타내는 데 사용됩니다. 이것은 다음과 같은 불쾌한 일을하는 것을 방지합니다.
#define TRUE FALSE //Happy debugging suckers
Erlang에서는 아톰을 파일에 저장하고, 다시 읽고, 원격 Erlang VM간에 와이어를 통해 전달할 수 있습니다.
예를 들어 몇 가지 용어를 파일에 저장 한 다음 다시 읽어 보겠습니다. 이것은 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
실제로 원자가 있음을 알 수 erlang_atom
있습니다.
lib_misc.erl
내용은 The Pragmatic Bookshelf에서 발행 한 Joe Armstrong의 "Programming Erlang : Software for a Concurrent World"에서 발췌 한 것입니다. 나머지 소스 코드는 여기에 있습니다 .
You're actually not right in saying python has no analogue to atoms or symbols. It's not difficult to make objects that behave like atoms in python. Just make, well, objects. Plain empty objects. Example:
>>> red = object()
>>> blue = object()
>>> c = blue
>>> c == red
False
>>> c == blue
True
>>>
TADA! Atoms in python! I use this trick all the time. Actually, you can go further than that. You can give these objects a type:
>>> class Colour:
... pass
...
>>> red = Colour()
>>> blue = Colour()
>>> c = blue
>>> c == red
False
>>> c == blue
True
>>>
Now, your colours have a type, so you can do stuff like this:
>>> type(red) == Colour
True
>>>
So, that's more or less equivalent in features to lispy symbols, what with their property lists.
In Ruby, symbols are often used as keys in hashes, so often that Ruby 1.9 even introduced a shorthand for constructing a hash. What you previously wrote as:
{:color => :blue, :age => 32}
can now be written as:
{color: :blue, age: 32}
Essentially, they are something between strings and integers: in source code they resemble strings, but with considerable differences. The same two strings are in fact different instances, while the same symbols are always the same instance:
> 'foo'.object_id
# => 82447904
> 'foo'.object_id
# => 82432826
> :foo.object_id
# => 276648
> :foo.object_id
# => 276648
This has consequences both with performance and memory consumption. Also, they are immutable. Not meant to be altered once when assigned.
An arguable rule of thumb would be to use symbols instead of strings for every string not meant for output.
Although perhaps seeming irrelevant, most code-highlighting editors colour symbols differently than the rest of the code, making the visual distinction.
The problem I have with similar concepts in other languages (eg, C) can be easily expressed as:
#define RED 1
#define BLUE 2
#define BIG 1
#define SMALL 2
or
enum colors { RED, BLUE };
enum sizes { BIG, SMALL };
Which causes problems such as:
if (RED == BIG)
printf("True");
if (BLUE == 2)
printf("True");
Neither of which really make sense. Atoms solve a similar problem without the drawbacks noted above.
Atoms provide fast equality testing, since they use identity. Compared to enumerated types or integers, they have better semantics (why would you represent an abstract symbolic value by a number anyway?) and they are not restricted to a fixed set of values like enums.
The compromise is that they are more expensive to create than literal strings, since the system needs to know all exising instances to maintain uniqueness; this costs time mostly for the compiler, but it costs memory in O(number of unique atoms).
Atoms are like an open enum, with infinite possible values, and no need to declare anything up front. That is how they're typically used in practice.
For example, in Erlang, a process is expecting to receive one of a handful of message types, and it's most convenient to label the message with an atom. Most other languages would use an enum for the message type, meaning that whenever I want to send a new type of message, I have to go add it to the declaration.
Also, unlike enums, sets of atom values can be combined. Suppose I want to monitor my Erlang process's status, and I have some standard status monitoring tool. I can extend my process to respond to the status message protocol as well as my other message types. With enums, how would I solve this problem?
enum my_messages {
MSG_1,
MSG_2,
MSG_3
};
enum status_messages {
STATUS_HEARTBEAT,
STATUS_LOAD
};
The problem is MSG_1 is 0, and STATUS_HEARTBEAT is also 0. When I get a message of type 0, what is it? With atoms, I don't have this problem.
Atoms/symbols are not just strings with constant-time comparison :).
'Programing' 카테고리의 다른 글
SQL Server VARBINARY 열에 Byte []를 삽입하는 방법 (0) | 2020.11.30 |
---|---|
코드베이스를 처음부터 다시 작성해야하는 경우 (0) | 2020.11.30 |
JavaScript에서 클래스를 추가 / 제거하는 방법은 무엇입니까? (0) | 2020.11.30 |
Qt에서 창의 제목을 변경하는 방법은 무엇입니까? (0) | 2020.11.30 |
많은 경고를 제공하는 Rails 4의 RSpec으로 보호 (0) | 2020.11.30 |