The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# No MMD yet.
method exists (Hash|Pair|Array $self: $idx) {
  if $self.isa("Hash") {
    JS::inline('new PIL2JS.Box.Constant(function (args) {
      var hash = args[1].FETCH(), key = args[2], cc = args.pop();
      cc(new PIL2JS.Box.Constant(hash.exists(key)));
    })')(%$self, $idx);
  } elsif $self.isa("Pair") {
    $self.key eq $idx;
  } elsif $self.isa("Array") {
    JS::inline('new PIL2JS.Box.Constant(function (args) {
      var array = args[1].FETCH(), idx = Number(args[2].toNative()), cc = args.pop();
      cc(new PIL2JS.Box.Constant(
        array[idx >= 0 ? idx : array.length + idx] != undefined
      ));
    })')(@$self, $idx);
  } else {
    die ".exists does not work on objects of type {$self.ref}!";
  }
}

method delete (Hash|Array $self: *@idx) {
  if $self.isa("Hash") {
    JS::inline('new PIL2JS.Box.Constant(function (args) {
      var hash = args[1].FETCH(), keys = args[2].FETCH(), cc = args.pop();
      var ret  = [];
      for(var i = 0; i < keys.length; i++) {
        var deleted = hash.delete_key(keys[i]);
        ret.push(deleted == undefined ? new PIL2JS.Box.Constant(undefined) : deleted);
      }
      cc(new PIL2JS.Box.Constant(ret));
    })')(%$self, @idx);
  } elsif $self.isa("Array") {
    JS::inline('new PIL2JS.Box.Constant(function (args) {
      var array = args[1].FETCH(), idxs = args[2].toNative(), cc = args.pop();
      var ret   = [];
      for(var i = 0; i < idxs.length; i++) {
        var idx = Number(idxs[i]) >= 0 ? Number(idxs[i]) : array.length + Number(idxs[i]);
        ret.push(array[idx] == undefined ? new PIL2JS.Box.Constant(undefined) : array[idx]);
        delete array[idx];
        if(idx == array.length - 1) array.length--;
      }
      cc(new PIL2JS.Box.Constant(ret));
    })')(@$self, @idx);
  } else {
    die ".delete does not work on objects of type {$self.ref}!";
  }
}

method keys (Hash|Pair|Array $self:) {
  if $self.isa("Hash") {
    JS::inline('new PIL2JS.Box.Constant(function (args) {
      var hash = args[1].FETCH();
      var cc   = args.pop();
      var keys = hash.keys();
      cc(new PIL2JS.Box.Constant(keys));
    })')(%$self);
  } elsif $self.isa("Pair") {
    ($self.key,);
  } elsif $self.isa("Array") {
    JS::inline('new PIL2JS.Box.Constant(function (args) {
      var array = args[1].FETCH();
      var cc    = args.pop();
      var ret   = [];
      for(var i = 0; i < array.length; i++) {
        ret.push(new PIL2JS.Box.Constant(i));
      }
      cc(new PIL2JS.Box.Constant(ret));
    })')(@$self);
  } else {
    die ".keys does not work on objects of type {$self.ref}!";
  }
}

method values (Hash|Pair|Array|Junction $self:) {
  if $self.isa("Junction") {
    JS::inline('new PIL2JS.Box.Constant(function (args) {
      var junc = args[1].FETCH(), cc = args.pop();
      cc(new PIL2JS.Box.Constant(junc.values));
    })')($self);
  } elsif $self.isa("Hash") {
    # $self.keys.map:{ $self{$_} } can't work, as map returns new containers.
    # (I think.)
    my @res;
    @res[+@res] := $self{$_} for $self.keys;
    @res;
  } elsif $self.isa("Pair") {
    ($self.value,);
  } elsif $self.isa("Array") {
    @$self;
  } else {
    die ".values does not work on objects of type {$self.ref}!";
  }
}

method kv (Hash|Pair|Array $self:) {
  if $self.isa("Hash") {
    my @res;
    for $self.keys {
        push @res, $_, undef;
        @res[-1] := $self{$_};
    }
    @res;
  } elsif $self.isa("Pair") {
    ($self.key, $self.value);
  } elsif $self.isa("Array") {
    my @res;
    for $self.keys {
        push @res, $_, undef;
        @res[-1] := $self[$_];
    }
    @res;
  } else {
    die ".kv does not work on objects of type {$self.ref}!";
  }
}

method pairs (Hash|Pair|Array $self:) {
  my sub make_pair ($key, $value is rw) is primitive is rw {
    my $pair = ($key => $value);
    $pair.value := $value;
    $pair;
  }

  if $self.isa("Hash") {
    $self.keys.map:{ make_pair($_, $self{$_}) };
  } elsif $self.isa("Pair") {
    ($self,);
  } elsif $self.isa("Array") {
    $self.keys.map:{ make_pair($_, $self[$_]) };
  } else {
    die ".pairs does not work on objects of type {$self.ref}!";
  }
}

method pick (Hash|Pair|Array|Junction $self:) {
  if $self.isa("Junction") {
    my @vals = $self.values;
    @vals[int rand @vals];
  } elsif $self.isa("Hash") {
    any($self.pairs).pick;
  } elsif $self.isa("Pair") {
    $self;
  } elsif $self.isa("Array") {
    any(@$self).pick;
  }
}