Подтвердить что ты не робот

Lisp и символы Erlang Atoms, Ruby и Scheme. Насколько они полезны?

Насколько полезной является функция наличия типа данных атома на языке программирования?

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

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

4b9b3361

Ответ 1

Краткий пример, показывающий, как умение манипулировать символами приводит к более чистому коду: (Код находится в Scheme, диалекте 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: Нежное введение в символические вычисления.

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

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

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

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

Ответ 5

В Lisp символ и атом представляют собой две разные и несвязанные понятия.

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

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

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

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

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

(snow snow)

то он создает список из двух cons-ячеек. ЦАР каждой минус-ячейки указывает на тот же символ снега. В памяти Lisp имеется только один символ.

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

Ответ 6

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

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

Пример может сделать это более ясным (с использованием схемы 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" - это строка.

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

Ответ 7

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

В Python [1], словарь.

d = dict(foo=1, bar=2)

В Perl [2], hash.

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

Ответ 8

Вы на самом деле не правы, говоря, что 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
>>> 

Если вы спросите меня, это на самом деле улучшение на лизирующихся символах.

Ответ 9

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

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

Там no Boolean type в 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 действительно содержит атом erlang_atom.


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

Ответ 10

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

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

Ответ 11

В 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 

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

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

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

Ответ 12

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

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

Ответ 13

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

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

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

enum my_messages {
  MSG_1,
  MSG_2,
  MSG_3
};

enum status_messages {
  STATUS_HEARTBEAT,
  STATUS_LOAD
};

Проблема MSG_1 равна 0, а STATUS_HEARTBEAT также равно 0. Когда я получаю сообщение типа 0, что это такое? С атомами у меня нет этой проблемы.

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