it-roy-ru.com

Атомы Лиспа и Эрланга, Ruby и ​​символы схемы. Насколько они полезны?

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

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

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

61
Muhammad Alkarouri

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

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

37
Vijay Mathew

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

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

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

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

53
I GIVE TERRIBLE ADVICE

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

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

Разница в том, что атомы обычно не нужно объявлять, и у них НЕТ базового представления, о котором нужно беспокоиться. atom monday в Erlang или Prolog имеет значение "atom monday" и ничего более или менее.

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

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

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

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

14
JUST MY correct OPINION

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

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

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

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

13
Greg Dan

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

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

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

Кроме того, тип ATOM совпадает с типом (NOT CONS).

Все, что не является конс-ячейкой, является atom в 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>

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

(snow snow)

затем он создает список из двух минусов. CAR каждой ячейки cons указывает на один и тот же символ снег. В памяти LISP есть только один символ.

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

12
Rainer Joswig

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

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

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

> (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 - не просто уловка; чтение выражений как структур данных и их преобразование каким-либо образом является основой макросов.

6
Hans Nowak

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

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

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

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

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

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

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

3
enigmaticPhysicist

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

В 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
tantalor

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

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

Там нет логического типа данных в Erlang . Вместо этого атомы 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 действительно содержит atom erlang_atom.


Содержимое lib_misc.erl взято из книги "Программирование на Erlang: программное обеспечение для параллельного мира" Джо Армстронга, изданной The Pragmatic Bookshelf. Остальной исходный код здесь .

3
Yasir Arsanukaev

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

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

2
ktr

В Ruby символы часто используются в качестве ключей в хешах, так часто, что в Ruby 1.9 даже вводится сокращение для создания хеша. То, что вы ранее написали как:

{:color => :blue, :age => 32}

теперь можно записать как:

{color: :blue, age: 32}

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

> 'foo'.object_id
# => 82447904 
> 'foo'.object_id
# => 82432826 
> :foo.object_id
# => 276648 
> :foo.object_id
# => 276648 

Это влияет как на производительность, так и на потребление памяти. Кроме того, они неизменны. Не предназначен для изменения один раз при назначении.

Спорное правило было бы использовать символы вместо строки для каждой строки не предназначен для вывода.

Хотя это может показаться неуместным, большинство редакторов с подсветкой кода по-разному отображают символы цвета, чем остальная часть кода, что делает визуальное различие.

2
Mladen Jablanović

Атомы подобны открытому перечислению, с бесконечными возможными значениями, и не нужно ничего объявлять заранее. Вот как они обычно используются на практике.

Например, в Erlang процесс ожидает получения одного из нескольких типов сообщений, и наиболее удобно пометить сообщение атомом. Большинство других языков используют перечисление для типа сообщения, а это означает, что всякий раз, когда я хочу отправить сообщение нового типа, я должен добавить его в объявление.

Кроме того, в отличие от перечислений, наборы значений 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, что это? С атомами у меня нет этой проблемы.

Атомы/символы - это не просто строки с постоянным сравнением :).

1
Sean

Атомы обеспечивают быстрое тестирование на равенство, поскольку они используют идентичность. По сравнению с перечисляемыми типами или целыми числами они имеют лучшую семантику (в любом случае, зачем вам представлять абстрактное символическое значение числом?), И они не ограничены фиксированным набором значений, таких как перечисления.

Компромисс состоит в том, что их создание дороже, чем буквальных строк, поскольку системе необходимо знать все существующие экземпляры, чтобы поддерживать уникальность; это стоит времени в основном для компилятора, но это стоит памяти в O (число уникальных атомов).

1
Damien Pollet