Атомы Лиспа и Эрланга, символы рубина и схемы.Насколько они полезны? - PullRequest
61 голосов
/ 02 февраля 2011

Насколько полезна возможность иметь тип данных atom в языке программирования?

Некоторые языки программирования имеют понятие атома или символа для представления своего рода константы. Есть несколько различий между языками, с которыми я сталкивался (Lisp, Ruby и Erlang), но мне кажется, что общая концепция одинакова. Я интересуюсь дизайном языка программирования, и мне было интересно, какое значение имеет наличие атомного типа в реальной жизни. Другие языки, такие как Python, Java, C #, кажутся вполне успешными без него.

У меня нет реального опыта работы с Lisp или Ruby (я знаю синтаксис, но не использовал ни в реальном проекте). Я использовал Erlang достаточно, чтобы привыкнуть к этой концепции.

Ответы [ 13 ]

53 голосов
/ 02 февраля 2011

Атомы - это литералы, константы с собственным именем для значения.То, что вы видите, это то, что вы получаете, и не ожидайте большего.Атом кошка означает «кошка» и все.Вы не можете играть с этим, вы не можете изменить его, вы не можете разбить его на куски;это кот.Смирись с этим.

Я сравнил атомы с константами, чье имя является их значением.Возможно, вы работали с кодом, который использовал константы ранее: например, допустим, у меня есть значения для цветов глаз: BLUE -> 1, BROWN -> 2, GREEN -> 3, OTHER -> 4.Вам необходимо сопоставить имя константы с некоторым базовым значением.Атомы позволяют забыть о базовых ценностях: мой цвет глаз может быть просто «синий», «коричневый», «зеленый» и «другой».Эти цвета могут использоваться где угодно в любом фрагменте кода: базовые значения никогда не будут конфликтовать, и такая константа не может быть неопределенной!

взято из http://learnyousomeerlang.com/starting-out-for-real#atoms

С учетом вышесказанного атомы в конечном итоге лучше подходят для описания данных в вашем коде в тех местах, где другие языки будут вынуждены использовать либо строки, перечисления, либо определения.Их безопаснее и удобнее использовать для получения аналогичных ожидаемых результатов.

37 голосов
/ 02 февраля 2011

Короткий пример, который показывает, как способность манипулировать символами приводит к более чистому коду: (Код находится на Схеме, диалекте Лиспа).

(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: Нежное введение в символические вычисления .

14 голосов
/ 02 февраля 2011

Атомы (в Erlang или Prolog и т. Д.) Или символы (в Lisp или Ruby и т. , Они занимают пространство перечислений в стиле C следующим образом:

enum days { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

Разница в том, что атомы обычно не нужно объявлять, и у них НЕТ основного представления, о котором нужно беспокоиться. Атом monday в Эрланге или Прологе имеет значение «атом monday» и ничего более или менее.

Хотя верно и то, что вы можете получить большую часть такого же использования от строковых типов, как и от атомов, у последнего есть некоторые преимущества. Во-первых, поскольку атомы гарантированно уникальны (за кулисами их строковые представления преобразуются в некую форму легко проверяемого идентификатора), гораздо быстрее их сравнивать, чем сравнивать эквивалентные строки. Во-вторых, они неделимы. Атом monday не может быть проверен, чтобы увидеть, заканчивается ли он, например, day. Это чистая неделимая семантическая единица. У вас меньше концептуальной перегрузки, чем в строковом представлении другими словами.

Вы также можете получить большую часть тех же преимуществ с перечислениями в стиле C. В частности, скорость сравнения, во всяком случае, выше. Но ... это целое число. И вы можете делать странные вещи, например, SATURDAY и SUNDAY переводить в одно и то же значение:

enum days { SATURDAY, SUNDAY = 0, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY }

Это означает, что вы не можете доверять разным «символам» (перечислениям) как разным вещам и, следовательно, значительно усложняете рассуждения о коде. Кроме того, отправка перечислимых типов по проводному протоколу проблематична, потому что нет никакого способа отличить их от обычных целых чисел. Атомы не имеют этой проблемы. Атом не является целым числом и никогда не будет выглядеть как закулисный.

13 голосов
/ 02 февраля 2011

Как программист C, у меня была проблема с пониманием того, что на самом деле представляют собой символы Ruby. Я был просветлен после того, как увидел, как символы реализованы в исходном коде.

Внутри Ruby-кода есть глобальная хеш-таблица, строки которой отображаются в целые числа. Все рубиновые символы хранятся там. Ruby интерпретатор на этапе разбора исходного кода использует эту хеш-таблицу для преобразования всех символов в целые числа. Тогда внутри все символы рассматриваются как целые числа. Это означает, что один символ занимает только 4 байта памяти, и все сравнения выполняются очень быстро.

Таким образом, вы можете рассматривать символы Ruby как строки, которые реализованы очень умным способом. Они выглядят как строки, но работают почти как целые числа.

Когда создается новая строка, тогда в Ruby создается новая структура C для хранения этого объекта. Для двух строк Ruby есть два указателя на две разные области памяти (которые могут содержать одну и ту же строку). Однако символ немедленно преобразуется в тип C int. Поэтому нет никакого способа различить два символа как два разных объекта Ruby. Это побочный эффект реализации. Просто помните об этом при кодировании и все.

12 голосов
/ 02 февраля 2011

В Лиспе символ и атом - это два разных и не связанных понятия.

Обычно в Лиспе ATOM не является конкретным типом данных. Это короткая рука для NOT CONS.

(defun atom (item)
  (not (consp item)))

Также тип ATOM совпадает с типом (НЕ CONS).

Все, что не является конс-ячейкой, является атомом в Common Lisp.

СИМВОЛ - это определенный тип данных.

Символ - это объект с именем и индивидуальностью. Символ может быть заключен в пакет . Символ может иметь значение, функцию и список свойств.

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>

В исходном коде на Лиспе идентификаторы переменных, функций, классов и т. Д. Записываются в виде символов. Если s-выражение Lisp читается читателем, оно создает новые символы, если они неизвестны (доступны в текущем пакете), или повторно использует существующий символ (если оно доступно в текущем пакете. Если читатель Lisp читает список вроде

(snow snow)

тогда он создает список из двух cons-ячеек. АВТОМОБИЛЬ каждой ячейки минусов указывает на один и тот же символ снег . В памяти Lisp есть только один символ.

Также обратите внимание, что plist (список свойств) символа может хранить дополнительную метаинформацию для символа. Это может быть автор, местоположение источника и т. Д. Пользователь также может использовать эту функцию в своих программах.

6 голосов
/ 02 февраля 2011

В Схеме (и других членах семейства Лисп) символы не просто полезны, они необходимы.

Интересным свойством этих языков является то, что они гомоичны . Программа или выражение Схемы само может быть представлено как действительная структура данных Схемы.

Пример может прояснить это (используя схему Гоша):

> (define x 3)
x
> (define expr '(+ x 1))
expr
> expr
(+ x 1)
> (eval expr #t)
4

Здесь expr - это просто список, состоящий из символа + , символа x и числа 1 . Мы можем манипулировать этим списком, как и любой другой, передавать его и т. Д. Но мы также можем оценивать его, в этом случае он будет интерпретирован как код.

Чтобы это работало, Схема должна иметь возможность различать символы и строковые литералы. В приведенном выше примере x является символом. Его нельзя заменить строковым литералом без изменения значения. Если мы возьмем список '(печать x) , где x - символ, и оценим его, это означает что-то еще, чем ' (печать "x") , где "x" - это строка.

Кстати, способность представлять выражения Scheme с использованием структур данных Scheme - не просто уловка; Чтение выражений как структур данных и их преобразование некоторым образом является основой макросов.

3 голосов
/ 16 марта 2015

Вы на самом деле не правы, говоря, что у питона нет аналогов атомам или символам.В python нетрудно создавать объекты, которые ведут себя как атомы.Просто сделай, ну, объекты.Обычные пустые предметы.Пример:

>>> red = object()
>>> blue = object()
>>> c = blue
>>> c == red
False
>>> c == blue
True
>>> 

ТАДА!Атомы в питоне!Я использую этот трюк все время.На самом деле, вы можете пойти дальше, чем это.Вы можете присвоить этим объектам тип:

>>> class Colour:
...  pass
... 
>>> red = Colour()
>>> blue = Colour()
>>> c = blue
>>> c == red
False
>>> c == blue
True
>>> 

Теперь у ваших цветов есть тип, поэтому вы можете делать такие вещи:

>>> type(red) == Colour
True
>>> 

Итак, это более или менее эквивалентно вфункции для букв шрифта, что с их списками свойств.

3 голосов
/ 02 февраля 2011

В некоторых языках литералы ассоциативных массивов имеют ключи, которые ведут себя как символы.

В 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 этот синтаксис недопустим, поскольку ключи должны быть заключены в кавычки. Я не знаю, как доказать, что они являются символами, потому что я не знаю, как читать память переменной.

3 голосов
/ 02 февраля 2011

Атомы гарантированно будут уникальными и цельными, в отличие от e. Например, значения констант с плавающей точкой, которые могут отличаться из-за неточностей при кодировании, отправке их по проводам, декодировании на другой стороне и преобразовании обратно в плавающую точку Независимо от того, какую версию интерпретатора вы используете, он гарантирует, что атом всегда будет иметь одинаковое «значение» и будет уникальным.

Erlang VM хранит все атомы, определенные во всех модулях, в глобальной таблице атомов .

В Erlang отсутствует тип данных Boolean. Вместо этого атомы true и false используются для обозначения логических значений. Это мешает делать такие неприятные вещи:

#define TRUE FALSE //Happy debugging suckers

В Erlang вы можете сохранять атомы в файлы, читать их обратно, передавать по проводам между удаленными виртуальными машинами Erlang и т. Д.

В качестве примера я сохраню пару терминов в файл, а затем прочту их обратно. Это исходный файл 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 содержание взято из "Программирование Erlang: Программное обеспечение для параллельного мира" Джо Армстронга, изданного The Pragmatic Bookshelf. Остальной исходный код здесь .

2 голосов
/ 02 февраля 2011

Проблема, с которой я сталкиваюсь в аналогичных понятиях на других языках (например, 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");

Ничего из этого не имеет смысла. Атомы решают аналогичную проблему без отмеченных выше недостатков.

...