Perl 6 中的 S/// 操作符

Perl 6: S/// 操作符

By Zoffix Znet

来自 Perl 5 背景的我, 第一次使用 Perl 6 的非破坏性替换操作符 S/// 的经历就像下面这样:

img

进展会更好的。我不但会改善错误信息,而且会解释当前的所有事情。

智能匹配

我有问题的原因是因为,看到外形相似的操作符,我就简单地把 Perl 5 中的绑定操作符(=~)转换为 Perl 6 中的智能匹配操作符(~~) 还期望它能正常工作。事实上我是异想天开。S/// 操作符没有文档,并且结合令人困惑的(那个时候)警告信息,这就是我痛苦的根源:

my $orig = 'meowmix';
my $new  = $orig ~~ S/me/c/;
say $new;

# OUTPUT warning:
# Smartmatch with S/// can never succeed

这个丑陋的警告说这儿的 ~~ 操作符是个错误的选择并且确实如此。~~ 操作符不是 Perl 5 的 =~ 操作符的等价物。~~ 智能操作符把它左边的东西起了个叫做 $_ 的别名,然后 ~~ 计算它右侧的东西,然后在右侧这个东西身上调用 .ACCEPTS($_) 方法。这就是所有的魔法。

所以上面的例子实际上发生了:

  • 我们到达 S/// 的时候, $orig 被起了个叫做 $_ 的别名。
  • S/// 非破坏性地在 $_ 身上执行了替换并返回那个结果字符串。这是智能匹配将要操作的东西。
  • 智能匹配,按照 Str 与 Str 相匹配的规则,会根据替换是否发生来给出 True 或 False(令人困惑的是,True 意味着没发生)

结果一路下来,我们并没有得到我们想要的:替换过的字符串。

使用 Given

既然我们知道了 S/// 总是作用在 $_ 上并且返回替换后的结果,很容易就想到几种方法把 $_ 设置为我们原来的字符串并把 S/// 的返回值收集回来,我们来看几个例子:

my $orig = 'meowmix';
my $new  = S/me/c/ given $orig;
say $orig;
say $new;

my @orig = <meow cow sow vow>;
my @new  = do for @orig { S/\w+ <?before 'ow'>/w/ };
say @orig;
say @new;

# OUTPUT:
# meowmix
# cowmix
# [meow cow sow vow]
# [wow wow wow wow]

第一个作用在单个值上。我们使用后置形式的 given 块儿,这让我们避免了花括号(你可以使用 with 代替 given 得到同样的结果)。given $orig 会给 $orig 起个叫做 $_ 的别名。从输出来看,原字符串没有被更改。

第二个例子作用在数组中的一堆字符串身上并且我们使用 do 关键字来执行常规的 for 循环(那种情况下,它把循环变量别名给 $_ 了)并把结果赋值给 @new 数组。再次,输出显示原来的数组并没有发生改变。

副词

S/// 操作符 -- 就像 s/// 操作符和某些方法一样 -- 允许你使用正则表达式副词:

given 'Lörem Ipsum Dolor Sit Amet' {
    say S:g      /m/g/;  # Löreg Ipsug Dolor Sit Aget
    say S:i      /l/b/;  # börem Ipsum Dolor Sit Amet
    say S:ii     /l/b/;  # Börem Ipsum Dolor Sit Amet
    say S:mm     /o/u/;  # Lürem Ipsum Dolor Sit Amet
    say S:nth(2) /m /g/; # Lörem Ipsug Dolor Sit Amet
    say S:x(2)   /m /g/; # Löreg Ipsug Dolor Sit Amet
    say S:ss/Ipsum Dolor/Gipsum\nColor/; # Lörem Gipsum Color Sit Amet
    say S:g:ii:nth(2) /m/g/;             # Lörem Ipsug Dolor Sit Amet
}

如你所见,它们以 :foo 的形式添加在操作符 S 这个部件的后面。你可以大大方方地使用空白符号并且几个副词可以同时使用。下面是它们的意义:

  • :g —(长形式::global)全局匹配:替换掉所有的出现
  • :i —不区分大小写的匹配
  • :ii —(长形式: :samecase) 保留大小写:不管用作替换字母的大小写,使用原来被替换的字母的大小写
  • :mm —(长形式::samemark) 保留重音符号:在上面的例子中,字母 o 上的分音符号被保留并被应用到替换字母 u 上
  • :nth(n) —只替换第 n 次出现的
  • :x(n) —至多替换 n 次(助记符: 'x' 作为及时)
  • :ss —(长形式:samespace)保留空白类型:空白字符的类型被保留,而不管替换字符串中使用的是什么空白字符。在上面的例子中,我们使用换行作为替换,但是原来的空白被保留了。

方法形式

S/// 操作符很好,但是有时候有点笨拙。不要害怕, Perl 6 提供了 .subst 方法能满足你所有的替换需求并且消除你对 .subst/.substr 的困惑。下面来看例子:

say 'meowmix'.subst: 'me', 'c';
say 'meowmix'.subst: /m./, 'c';

# OUTPUT:
# cowmix
# cowmix

这个方法要么接收一个正则表达式要么接收一个普通的字符串作为它的第一个位置参数,它是要在调用者里面("meowmix")查找的东西。第二个参数是替换字符串。

通过简单地把它们列为具名 Bool 参数,你也可以使用副词。在 S/// 形式中, 副词 :ss:ii 分别表明 :s(使空白有意义) 的出现和 :i(不区分大小写的匹配) 的出现。在方法形式中,你必须把这些副词应用到正则表达式自身身上:

given 'Lorem Ipsum Dolor Sit Amet' {
    say .subst: /:i l/, 'b', :ii;
    say .subst: /:s Ipsum Dolor/, "Gipsum\nColor", :ss;
}

# OUTPUT:
# Borem Ipsum Dolor Sit Amet
# Lorem Gipsum Color Sit Amet

方法形式的捕获

捕获对于替换操作来说不陌生,所以我们来尝试捕获下方法调用形式的替换:

say 'meowmix'.subst: /me (.+)/, "c$0";

# OUTPUT:
# Use of Nil in string context  in block <unit> at test.p6 line 1
# c

不是我们要找的。我们的替换字符串构建在达到 .subst 方法之前,并且里面的 $0 变量实际上指向任何这个方法调用之前的东西,而不是 .subst 正则表达式中的捕获。所以我们怎么来修正它呢?

.subst 方法的第二个参数也可以接受一个 Callable。在它里面,你可以使用 $0, $1, ... $n 变量,直到你想要的编号,并从捕获中得到正确的值:

say 'meowmix'.subst: /me (.+)/, -> { "c$0" };

# OUTPUT:
# cowmix

这里,我们为我们的 Callable 使用了尖号块儿,但是 WhateverCode 和子例程也有效。每次替换都会调用这个 Callable,并且把 Match 对象作为第一个位置参数传递给 Callable, 如果你需要访问它的话。

结论

S/// 操作符在 Perl 6 中是 s/// 操作符的战友,它不是修改原来的字符串,而是拷贝原来的字符串,修改,然后返回修改过的版本。这个操作符的使用方式跟 Perl 5 中的非破坏性替换操作符的使用方式不同。作为备选, 方法版本的 .subst 也能使用。 方法形式和操作符形式的替换都能接收一组副词以修改它们的行为,来满足你的需求。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 允许的修饰符 有些修饰符能在所有允许的地方出现, 但并非所有的都这样. 通常, 影响 regex 编译的修饰符(...
    焉知非鱼阅读 1,318评论 0 1
  • 从匹配中返回值 Match 对象 成功的匹配总是返回一个 Match 对象, 这个对象通常也被放进 $/ 中, (...
    焉知非鱼阅读 1,775评论 0 1
  • 标题 Synopsis 5: Regexes and Rules 版本 不论何时, 在 grammar 中引用递归...
    焉知非鱼阅读 979评论 0 0
  • 2009 有用的和有意思的循环 让我们来看一个基本的例子. 这是一个最简单清晰的语法的例子.在这并没有使用括号来包...
    焉知非鱼阅读 534评论 0 0
  • “先生……先生……”一个很艰难的声音,吐词似乎被某种东西严重阻隔了。仔细听,当然在离声音有七八米的后面判断,是看不...
    禅静一生阅读 385评论 1 2