2017-07-28 48 views
3

比方说,我们有以下multi sub类型数组

multi sub abc(Int  @array) { say 10, ' ', @array; } 

multi sub abc(Array[Int] @array) { say 20, ' ', @array; } 

multi sub abc(Str  @array) { say 30, ' ', @array; } 

multi sub abc(Array[Str] @array) { say 40, ' ', @array; } 

正如this question提到的,调用这些有类型的数组可以得到详细:

abc Array[Int].new([1,2,3]); 

abc Array[Array[Int]].new([Array[Int].new([1,2,3]), Array[Int].new([2,3,4])]); 

这将是很好如果类型可以从字面上推断出来,我们可以这样做:

abc typed([1,2,3]); 

abc typed([[1,2,3],[2,3,4]]); 

abc typed(['a', 'b', 'c']); 

abc typed([['a', 'b', 'c'], ['b', 'c', 'd']]); 

进一步说,让我们添加它执行的类型推断我们的条款:

multi sub abc(@array) { abc typed(@array); } 

现在我们可以得到充分的推理,没有额外征收的语法:

abc [1,2,3]; 

abc [[1,2,3],[2,3,4]]; 

abc ['a', 'b', 'c']; 

abc [['a', 'b', 'c'], ['b', 'c', 'd']]; 

上面显示以下内容:

10 [1 2 3] 
20 [[1 2 3] [2 3 4]] 
30 [a b c] 
40 [[a b c] [b c d]] 

下面是一个简单的版本typed其对作品:

  • Array[Int]
  • Array[Array[Int]]
  • Array[Str]
  • Array[Array[Str]]

我的问题是,你将如何去实现这种类型推断的?有更好的方法吗?是否有类似的设施?

sub type-of(\obj) 
{ 
    if obj.^name eq 'Array' 
    { 
     if obj.map({ type-of($_).^name }).all eq obj.map({ type-of($_).^name })[0] 
     { 
      my $type = type-of(obj[0]); 
      return Array[$type]; 
     } 
     return Array; 
    } 

    if obj.^name eq 'Int' { return Int; } 
    if obj.^name eq 'Str' { return Str; } 
} 

sub typed(\obj) 
{ 
    if obj.^name eq 'Array' 
    { 
     return type-of(obj)(obj.List.map({ $_.&typed }).Array); 
    } 

    return (type-of(obj))(obj); 
} 

回答

1

调用这些与类型数组可以得到详细的

也许你过分了一点对一切类型的约束?

您还可以存储类型在一个常数,缩短你的代码位:

my constant AI = Array[Int]; 
dd AI.new([1,2,3]); 
dd Array[AI].new([AI.new([1,2,3]), AI.new([2,3,4])]); 

我的问题是,你将如何去实现这种类型推断的?

.WHAT pseudo-method返回调用者的类型对象。因此,您可以使用它来获取事物的类型,并且您可以使用=:= container identity operator来确定您是否正在处理Array。 (或者,您可以使用~~ smartmatch来代替Array的子类)。

这里是这样使用的样本实现,使用自定义操作CIRCUMFIX:

sub circumfix:<♥[ ]> (|c) { 
    my \array = circumfix:<[ ]>(|c); 
    return array unless try array.elems; 

    (my $type := array.head.WHAT) =:= Array 
     and $type := (array[0] = circumfix:<♥[ ]>(array.head<>)).WHAT; 

    my $type-it := True; 
    for array.tail: *-1 { 
     (my $el-type := .WHAT) =:= Array 
      and $el-type := ($_ = circumfix:<♥[ ]>(.<>)).WHAT; 
     next if $el-type =:= $type; 
     $type-it := False; 
    } 
    $type-it ?? Array[$type].new: array !! array 
} 

dd ♥[<1 2 3>]; 
dd ♥[<a b c>]; 
dd ♥[[1e0,2], [2,3], [3,3]]; 
dd ♥[[1,2], [2,3], [3,3]]; 
dd ♥[[[[1],],],]; 

# OUTPUT: 
# Array[IntStr].new(IntStr.new(1, "1"), IntStr.new(2, "2"), IntStr.new(3, "3")) 
# Array[Str].new("a", "b", "c") 
# [[1e0, 2], Array[Int].new(2, 3), Array[Int].new(3, 3)] 
# Array[Array[Int]].new(Array[Int].new(1, 2), Array[Int].new(2, 3), Array[Int].new(3, 3)) 
# Array[Array[Array[Array[Int]]]].new(Array[Array[Array[Int]]].new(Array[Array[Int]].new(Array[Int].new(1)))) 

我们使用|c to capture the args,只是给他们的核心[ ] CIRCUMFIX,这将使经常Array我们。

然后,我们try,看看我们得到了Array有任何元素:如果它不,我们不知道如何parametarize它和懒惰的东西不会让我们找出.elems,因此try

此时,我们知道我们的Array中至少有一个元素,所以我们抓住它并使用.WHAT来获取其类型。然后,我们使用=:=来检查它是否为Array,如果是,我们简单地对它进行递归,存储结果,将$type更新为返回的类型。该<>位仅仅是decont the Array

(my $type := array.head.WHAT) =:= Array 
     and $type := (array[0] = circumfix:<♥[ ]>(array.head<>)).WHAT; 

接下来,我们有for循环。我们从第二个元素开始,因为当我们确定要使用哪种类型时,我们已经考虑了第一个元素。

如果有任何其他元素都是相同的类型$type我们不能parametarize我们Array,所以我们只需循环,并检查它们的类型,用递归做同样的业务上Arrays的不是。如果我们发现任何不同的类型,我们设置一个标志($type-it),以指示当前的Array不应该被参数化(我们仍然保持循环,以便我们对任何剩余的Array s进行递归和参数化)。最后,如果标志被设置,我们使用$type参数化一个新的Array;否则我们会按原样返回我们的array

Simples :)


PS:这是值得注意的Perl 6的支持自我指涉Arrays,和上面的代码将与,比如说挂,这种设置:

my @a; @a[0] = @a; 
dd ♥[@a]; 

你需要实施标志着已经看到的事情的东西; something similar to this(如果您将NQP代码转换为纯Perl 6)