第五天 - 变量

Variables

这么简单的事,不是吗? 变量是一个保存着值的名字。

有时候,它持有的值可能会被另一个值所取代 - 因此就是名字。 (根据外科医生的说法,没有经常变化的变量应该看医生,并要求被诊断为常数。)

虽然它们很容易掌握,而且基本上每种语言都有它们,但我今天的目标是让你相信变量实际上非常棘手。 好的方式! 我的目的是让你被这篇博文绊倒,茫然,喃喃自语“我以为我知道变量,但我真的不知道……”。

接近最后,实验语言007也将会出现,我考虑变量这么多完全是这种语言的过错。

左还是右?

变量奇怪的第一种方式是它们以两种完全不同的方式使用。

my $x = "Christmas";

say("Merry " ~ $x);       # reading
$x = "Easter";            # writing

有时我们使用变量来读取值,有时我们使用它们来写一个值。但在这两种情况下,语法看起来完全一样!一些较旧的语言(例如Forth)实际上对这两种用法有不同的语法,我喜欢它们。但是这样的惯例似乎并没有幸存到现代。

相反,我们通过语法位置来区分这两种用法。如果你在赋值的左侧,那么你就被写了。否则,你正在被读取。

在文献中,这两种用途分别称为 lvaluesrvalues。分别为“左”和“右”。

Rvalues 非常正常,与我们对变量的一般考虑方式相对应;他们只是计算它们包含的值。然而,Lvalues 很奇怪。它们更像是盒子,你可以把东西放入(或内存位置?引用?),或者如果不是盒子本身,那么分离的能力放入其中。如果 lvalues 有一个类型,它看起来像 (T) -> void,接受 T 但不返回任何东西的东西。

参数

变量对现代编程至关重要。 还有一个原则表明它们完全没有必要。

那就对了! Tennent 的通信原则! (我知道你在想什么。不,我说的不是那个 Tennant

这个原则主要指向一种在程序中重写所有变量声明的方法,因此它们是参数声明。 一个例子应该足以展示一般原则:

# Before
my $veggie = "potato";
say "$veggie, and that's all I have to say about that!";

# After
(-> $veggie {
    say "$veggie, and that's all I have to say about that!";
})("potato");

看看变量声明如何变成参数声明,相应的赋值转变为参数?有经验的(或者我应该说是饱受争吵蹂躏的)JavaScript开发人员将这种结构视为 IIFE

由于我们总能进行这种转换,因此我们并不需要变量。只有参数。我主要是告诉你这个,所以你可以有点特别感谢你不必用参数编写你的代码。

关于Tennent的通信原则的最后注释:它的原始用法在维基百科上有简要描述。它基本上被遗忘了,直到Java即将获得闭包并且它的名称被调用并且原则被过度使用了一些,也许。

动态范围

在Raku中,只要变量范围偏离词法范围,变量就会产生额外的“twigil”(sigil之后的可选符号)。这些替代范围中最重要的可能只是动态范围。

同样,我们最好用一个例子来说明差异:

my $lexical = "mainline";
my $*dynamic = "mainline";

sub foo() {
    my $lexical = "foo";
    my $*dynamic = "foo";
    bar();
}

sub bar() {
    say $lexical;       # "mainline"
    say $*dynamic;      # "foo"
}

foo();

忘记处女座和Saggitarius以及其他占星术的迹象。对于你更深层次的个性而言,值得做的唯一区别就是你是在做词法查找还是动态查找。毕竟,只有两种人。

无论我们喜不喜欢,查找都是一个过程。我们给了一个名字,然后我们去找相应的值。我知道,这令人沮丧。但无论如何,让我们这样做,看看它在哪里。

对于 $lexical,通过查看程序文本本身来进行查找。该变量是否定义在我们所在的最小范围内,那个 bar sub? (事实并非如此。)然后我们向外走,直到周围的范围 - 这最终成为整个计划的范围。它是在那里定义的吗?是!真幸运的是我们从查找中获得胜利,其值为 “mainline”

$*dynamic - 请注意名字中的星号?我告诉过你有占星术! - 我们也从最里面的范围,bar sub开始,并在那里寻找定义。 (我们找不到。)但现在发生了一些不同的事情。我们不会向外跟随块结构,而是向上跟随调用链。谁调用给我们? foo。这就是我们的目标。那里有定义吗?是!所以我们已经完成并且成功了。

从历史的角度来看,动态查找是“明显的”,大多数语言最初都有它。词汇查找只是逐渐证明了它的价值,现在已成为流行病。 Perl 5实际上跨越了这段历史,而我的变量是词法,但较旧的我们/包变量是动态的。这就是你在历史发生时从身边得到的东西。

在Raku中,我们也通过禁止术语“父范围”来履行自己的职责。在一个词法和动态查找的世界里,它太混乱了。相反,我们更喜欢术语 OUTER(用于词法查找)和CALLER(用于动态查找)。

如果可能的话,Raku中的一些结构(例如returnnext)会尝试词法,但如果找不到任何词汇周围的东西来“附加”,则会回归到动态。这种类型的行为似乎没有真正的学术术语,所以Raku的概要称它为“lexotic”。

宏里面的变量

还在我这儿?礼包。 我们来谈谈宏。

use experimental :macros;

macro moo {
    my $counter = 0;
    quasi {
        say ++$counter;
    }
}

for ^10 {
    moo;
}

这是一个简单的宏,只是将代码中的 ++$counter 注入到 for 循环中。该程序将在各行上打印从1到10的所有数字。

很好,但……怎么样?请注意,宏扩展代码引用 $counter,但词法查找(如上所述)将找不到在周围词法范围内声明的变量。但是,这个程序仍然有效,或者更确切地说,是有效的。

那么使程序运作的基本原则是什么呢?事实证明,通过一个非常幸运的偶然事件,在宏体内定义的变量可以被“无法统一”并被左值替换。注入的代码说 ++$counter 实际上看起来更像是 ++☐,其中 代表那个(代表不可代理的)左值。

我知道这是一件小事,但是当我最终把它放在一起时我很高兴。事实上,我很高兴我把它写成github iuuse,只是为了确保细节都能解决。请继续关注此项的实现,从而保持卫生。

(对于那些在家中保持分数的人来说,卫生宏是本拨款申请中的里程碑D3。)

需要明确的是 - 这更像是一种实现意图。 Raku(和007)尚未实现完全卫生。但是,拥有明确的前进道路令人振奋。

无论如何,这是变数。他们很可爱,有点奇怪,但最后我们很高兴他们在那里。快乐的历险。☺

comments powered by Disqus