ruby - Lisp和Erlang原子,Ruby和Scheme符​​号。 他们有多有用?




7 Answers

一个简短的例子,显示操纵符号的能力如何导致更干净的代码:(代码在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:符号计算的温柔介绍 ”一书中找到。

在编程语言中使用原子数据类型的功能有多有用?

一些编程语言具有原子或符号的概念来表示各种常量。 我遇到的语言(Lisp,Ruby和Erlang)有一些差异,但在我看来,一般概念是相同的。 我对编程语言设计感兴趣,并且我想知道在现实生活中具有原子类型的价值。 其他语言如Python,Java,C#在没有它的情况下似乎表现得相当好。

我没有真正的Lisp或Ruby经验(我知道这些语法,但并没有用在真正的项目中)。 我已经使用Erlang足以适应那里的概念。




当你的语义值没有自然的底层“本地”表示时,原子(在Erlang或Prolog等中)或符号(在Lisp或Ruby等中) - 从此处仅称为原子 - 非常有用。 他们采用这样的C样式枚举的空间:

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

不同之处在于原子通常不需要声明,并且它们没有任何基础表示形式需要担心。 Erlang或Prolog中的原子monday具有“原子monday ”的值,并且不多或少。

虽然你可以从字符串类型中获得与原子不同的用途,但后者有一些优点。 首先,因为原子保证是唯一的(在幕后他们的字符串表示被转换成某种形式的易于测试的ID),所以比较它们比比较等效字符串要快得多。 其次,它们是不可分割的。 例如,原子monday不能被测试以查看它是否在day结束。 它是一个纯粹的,不可分割的语义单位。 换句话说,你的概念重载比你在字符串表示中的概念要少。

你也可以用C样式枚举获得很多相同的好处。 特别是比较速度,如果有的话,更快。 但是...这是一个整数。 你可以做奇怪的事情,比如SATURDAYSUNDAY翻译成同样的价值:

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

这意味着你不能相信不同的“符号”(枚举)是不同的东西,因此使代码的推理变得更加困难。 而且,通过有线协议发送枚举类型是有问题的,因为无法区分它们和常规整数。 原子没有这个问题。 原子不是一个整数,在幕后永远不会看起来像。




在Lisp中, 符号原子是两个不同的和不相关的概念。

通常在Lisp中,ATOM不是特定的数据类型。 这是NOT CONS的简称。

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

此外,ATOM类型与类型(NOT CONS)相同。

任何不是cons cell的东西都是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表达式,如果他们不知道(在当前包中可用),或者重新使用现有符号(如果在当前包中可用),它确实会创建新符号。如果Lisp读取器读取列表如

(snow snow)

那么它会创建一个两个cons单元的列表。 每个利弊细胞的CAR指向相同的符号 。 Lisp内存中只有一个符号。

另请注意,符号的plist(属性列表)可以存储符号的其他元信息。 这可能是作者,源位置等。用户也可以在他/她的程序中使用此功能。




在某些语言中,关联数组文字具有行为像符号的键。

在Python [1]中,是一本字典。

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

在Perl [2]中,一个散列。

my %h = (foo => 1, bar => 2);

在JavaScript [3]中,一个对象。

var o = {foo: 1, bar: 2};

在这些情况下, foobar就像符号一样,即不带引号的不可变字符串。

[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中,这种语法是不允许的,因为键必须被引用。 我不知道如何证明它们是符号,因为我不知道如何读取变量的内存。




与例如浮点常量值相比,原子保证是唯一的和整数的,这些值可能因编码时的不准确性而不同,通过电线发送,在另一端解码并转换回浮点。 无论您使用何种版本的解释器,它都能确保原子始终具有相同的“值”并且是唯一的。

Erlang VM将所有模块中定义的所有原子存储在全局原子表中

Erlang中没有布尔数据类型 。 相反,原子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内容摘自Joe Armstrong编着的“编程Erlang:并发世界的软件”,由The Pragmatic Bookshelf出版。 其余的源代码在这里




在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 

这对性能和内存消耗都有影响。 而且,它们是不变的。 分配后不打算改动一次。

一个有争议的经验法则是对于不用于输出的每个字符串,使用符号而不是字符串。

虽然也许看起来不相关,但大多数代码高亮编辑器的颜色符号与其他代码颜色不同,因此可以区分视觉效果。




原子就像一个开放的枚举,具有无限的可能值,并且不需要事先声明任何东西。 这就是他们通常在实践中使用的方式。

例如,在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的消息时,它是什么? 用原子,我没有这个问题。

原子/符号不仅仅是具有恒定时间比较的字符串:)。




Related

ruby erlang scheme lisp