最近关于Raku中Mixins的StackOverflow的问题激起了我的兴趣,即Mixins是否可以应用于复制其他编程语言中存在的功能。
例如,在R编程语言中,可以为向量的元素命名(即属性),这对于数据分析非常方便。有关一个很好的示例,请参阅:Andrie de Vries和Joris Meys的“如何在R中为您的向量中的值命名”,他们使用R
的内置岛
数据集来说明此功能。下面是一个更平淡无奇的示例(在R-REPL中运行的代码):
> #R-code
> x <- 1:4
> names(x) <- LETTERS[1:4]
> str(x)
Named int [1:4] 1 2 3 4
- attr(*, "names")= chr [1:4] "A" "B" "C" "D"
> x
A B C D
1 2 3 4
> x[1]
A
1
> sum(x)
[1] 10
下面我尝试使用de Vries和Meys使用的相同岛
数据集复制R
的“命名向量”。虽然下面的脚本运行并且(通常,参见下面的#3)产生所需/预期的输出,但我在底部留下了三个主要问题:
#Raku-script below;
put "Read in data.";
my $islands_A = <11506,5500,16988,2968,16,184,23,280,84,73,25,43,21,82,3745,840,13,30,30,89,40,33,49,14,42,227,16,36,29,15,306,44,58,43,9390,32,13,29,6795,16,15,183,14,26,19,13,12,82>.split(","); #Area
my $islands_N = <<"Africa" "Antarctica" "Asia" "Australia" "Axel Heiberg" "Baffin" "Banks" "Borneo" "Britain" "Celebes" "Celon" "Cuba" "Devon" "Ellesmere" "Europe" "Greenland" "Hainan" "Hispaniola" "Hokkaido" "Honshu" "Iceland" "Ireland" "Java" "Kyushu" "Luzon" "Madagascar" "Melville" "Mindanao" "Moluccas" "New Britain" "New Guinea" "New Zealand (N)" "New Zealand (S)" "Newfoundland" "North America" "Novaya Zemlya" "Prince of Wales" "Sakhalin" "South America" "Southampton" "Spitsbergen" "Sumatra" "Taiwan" "Tasmania" "Tierra del Fuego" "Timor" "Vancouver" "Victoria">>; #Name
"----".say;
put "Count elements (Area): ", $islands_A.elems; #OUTPUT 48
put "Count elements (Name): ", $islands_N.elems; #OUTPUT 48
"----".say;
put "Create 'named vector' array (and output):\n";
my @islands;
my $i=0;
for (1..$islands_A.elems) {
@islands[$i] := $islands_A[$i] but $islands_N[$i].Str;
$i++;
};
say "All islands (returns Area): ", @islands; #OUTPUT: returns 48 areas (above)
say "All islands (returns Name): ", @islands>>.Str; #OUTPUT: returns 48 names (above)
say "Islands--slice (returns Area): ", @islands[0..3]; #OUTPUT: (11506 5500 16988 2968)
say "Islands--slice (returns Name): ", @islands[0..3]>>.Str; #OUTPUT: (Africa Antarctica Asia Australia)
say "Islands--first (returns Area): ", @islands[0]; #OUTPUT: 11506
say "Islands--first (returns Name): ", @islands[0]>>.Str; #OUTPUT: (Africa)
put "Islands--first (returns Name): ", @islands[0]; #OUTPUT: Africa
put "Islands--first (returns Name): ", @islands[0]>>.Str; #OUTPUT: Africa
>
有没有更简单的方法来编写Misin循环…$islands_A[$i]但$islands_N[$i]. str;
?可以完全消除循环吗?
命名向量
或nvec
包装器是否可以写在put
周围,它将以与R相同的方式返回(name)\n(value)
,即使对于单个元素也是如此?Raku的Pair
方法在这里可能有用吗?
与上面的#2相关,在单元素@Island[0]
上调用put
会返回名称Africa
而不是Area值11506
。[注意,调用say
时不会发生这种情况]。是否有任何简单的代码可以实现以确保put
始终返回(数字)value
或始终返回(Misin)name
用于数组的全长度切片?
>
有更简单的方法吗?是的,使用zip元运算符Z
与infix结合使用,但是
my @islands = $islands_A[] Z[but] $islands_N[];
你为什么不修改数组来改变格式?
把
调用。str
在它得到的值上,say
调用. gist
如果您希望put
输出一些特定的文本,请确保。Str
方法输出该文本。
不过,我不认为您真的想要输出该格式。我认为您希望say
输出该格式。那是因为say
是供人类理解的,而您希望它对人类更好。
当你有一个“拉库能做X”的问题时,答案是不变的,是的,这只是工作量的问题,以及那时你是否还会称之为拉库。
你真正想问的问题是做X有多容易。
我去实现了你提供的那个链接。
请注意,这只是我在睡觉前创建的一个快速实现。所以把这看作是第一个草稿。
如果我真的要这么做,我可能会把它扔掉,在花了几天时间学习足够的R来弄清楚它实际上在做什么之后重新开始。
class NamedVec does Positional does Associative {
has @.names is List;
has @.nums is List handles <sum>;
has %!kv is Map;
class Partial {
has $.name;
has $.num;
}
submethod TWEAK {
%!kv := %!kv.new: @!names Z=> @!nums;
}
method from-pairlist ( +@pairs ) {
my @names;
my @nums;
for @pairs -> (:$key, :$value) {
push @names, $key;
push @nums, $value;
}
self.new: :@names, :@nums
}
method from-list ( +@list ){
my @names;
my @nums;
for @list -> (:$name, :$num) {
push @names, $name;
push @nums, $num;
}
self.new: :@names, :@nums
}
method gist () {
my @widths = @!names».chars Zmax @!nums».chars;
sub infix:<fmt> ( $str, $width is copy ){
$width -= $str.chars;
my $l = $width div 2;
my $r = $width - $l;
(' ' x $l) ~ $str ~ (' ' x $r)
}
(@!names Zfmt @widths) ~ "\n" ~ (@!nums Zfmt @widths)
}
method R-str () {
chomp qq :to/END/
Named num [1:@!nums.elems()] @!nums[]
- attr(*, "names")= chr [1:@!names.elems()] @!names.map(*.raku)
END
}
method of () {}
method AT-POS ( $i ){
Partial.new: name => @!names[$i], num => @!nums[$i]
}
method AT-KEY ( $name ){
Partial.new: :$name, num => %!kv{$name}
}
}
multi sub postcircumfix:<{ }> (NamedVec:D $v, Str:D $name){
$v.from-list: callsame
}
multi sub postcircumfix:<{ }> (NamedVec:D $v, List \l){
$v.from-list: callsame
}
my $islands_A = <11506,5500,16988,2968,16,184,23,280,84,73,25,43,21,82,3745,840,13,30,30,89,40,33,49,14,42,227,16,36,29,15,306,44,58,43,9390,32,13,29,6795,16,15,183,14,26,19,13,12,82>.split(","); #Area
my $islands_N = <<"Africa" "Antarctica" "Asia" "Australia" "Axel Heiberg" "Baffin" "Banks" "Borneo" "Britain" "Celebes" "Celon" "Cuba" "Devon" "Ellesmere" "Europe" "Greenland" "Hainan" "Hispaniola" "Hokkaido" "Honshu" "Iceland" "Ireland" "Java" "Kyushu" "Luzon" "Madagascar" "Melville" "Mindanao" "Moluccas" "New Britain" "New Guinea" "New Zealand (N)" "New Zealand (S)" "Newfoundland" "North America" "Novaya Zemlya" "Prince of Wales" "Sakhalin" "South America" "Southampton" "Spitsbergen" "Sumatra" "Taiwan" "Tasmania" "Tierra del Fuego" "Timor" "Vancouver" "Victoria">>;
# either will work
#my $islands = NamedVec.from-pairlist( $islands_N[] Z=> $islands_A[] );
my $islands = NamedVec.new( names => $islands_N, nums => $islands_A );
put $islands.R-str;
say $islands<Asia Africa Antarctica>;
say $islands.sum;
命名向量本质上是将向量与从名称到整数位置的映射相结合,并允许您按名称寻址元素。命名向量会改变向量的行为,而不是其元素的行为。所以在Raku中,我们需要为数组定义一个角色:
role Named does Associative {
has $.names;
has %!index;
submethod TWEAK {
my $i = 0;
%!index = map { $_ => $i++ }, $!names.list;
}
method AT-KEY($key) {
with %!index{$key} { return-rw self.AT-POS($_) }
else { self.default }
}
method EXISTS-KEY($key) {
%!index{$key}:exists;
}
method gist() {
join "\n", $!names.join("\t"), map(*.gist, self).join("\t");
}
}
multi sub postcircumfix:<[ ]>(Named:D \list, \index, Bool() :$named!) {
my \slice = list[index];
$named ?? slice but Named(list.names[index]) !! slice;
}
multi sub postcircumfix:<{ }>(Named:D \list, \names, Bool() :$named!) {
my \slice = list{names};
$named ?? slice but Named(names) !! slice;
}
混合在此角色中为您提供了R命名向量的大部分功能:
my $named = [1, 2, 3] but Named<first second last>;
say $named; # OUTPUT: «first␉second␉last1␉2␉3»
say $named[0, 1]:named; # OUTPUT: «first␉second1␉2»
say $named<last> = Inf; # OUTPUT: «Inf»
say $named<end>:exists; # OUTPUT: «False»
say $named<last end>:named; # OUTPUT: «last␉endInf␉(Any)»
由于这只是一个概念证明,命名
角色不能很好地处理不存在元素的命名。它也不支持修改名称切片。它可能确实支持创建一个可以混合到多个列表中的双关语。
请注意,此实现依赖于下标运算符是multis的未记录事实。如果您想将角色和运算符放在单独的文件中,您可能希望将is export
trait应用于运算符。
这可能不是最理想的方法(或者你特别想要的),但是当我看到这个特定问题的陈述时,首先想到的是Raku的同种异形,它是具有两个相关值的类型,可以根据上下文单独访问。
my $areas = (11506,5500,16988,2968,16,184,23,280,84,73,25,43,21,82,3745,840,13,30,30,89,40,33,49,14,42,227,16,36,29,15,306,44,58,43,9390,32,13,29,6795,16,15,183,14,26,19,13,12,82);
my $names = <"Africa" "Antarctica" "Asia" "Australia" "Axel Heiberg" "Baffin" "Banks" "Borneo" "Britain" "Celebes" "Celon" "Cuba" "Devon" "Ellesmere" "Europe" "Greenland" "Hainan" "Hispaniola" "Hokkaido" "Honshu" "Iceland" "Ireland" "Java" "Kyushu" "Luzon" "Madagascar" "Melville" "Mindanao" "Moluccas" "New Britain" "New Guinea" "New Zealand (N)" "New Zealand (S)" "Newfoundland" "North America" "Novaya Zemlya" "Prince of Wales" "Sakhalin" "South America" "Southampton" "Spitsbergen" "Sumatra" "Taiwan" "Tasmania" "Tierra del Fuego" "Timor" "Vancouver" "Victoria">;
my @islands;
for (0..^$areas) -> \i {
@islands[i] := IntStr.new($areas[i], $names[i]);
}
say "Areas: ", @islands>>.Int;
say "Names: ", @islands>>.Str;
say "Areas slice: ", (@islands>>.Int)[0..3];
say "Names slice: ", (@islands>>.Str)[0..3];
say "Areas first: ", (@islands>>.Int)[0];
say "Names first: ", (@islands>>.Str)[0];