6

The Raku docs say that Code.assuming

Returns a Callable that implements the same behavior as the original, but has the values passed to .assuming already bound to the corresponding parameters.

What is the difference between using .assuming and wrapping the Code in an anonymous Block (or Sub) that calls the inner function with some parameters already bound?

For instance, in the code below, what is the difference between &surname-public (an example the docs provide for .assuming) and &surname-block;


sub longer-names ( $first, $middle, $last, $suffix ) {
    say "Name is $first $middle $last $suffix";
}

my &surname-public = &longer-names.assuming( *, *, 'Public', * );
my &surname-block  = -> $a,$b,$c { longer-names($a, $b, 'Public', $c) }

surname-public( 'Joe', 'Q.', 'Jr.');  # OUTPUT: «Name is Joe Q. Public Jr.»
surname-block(  'Joe', 'Q.', 'Jr.');  # OUTPUT: «Name is Joe Q. Public Jr.»

I see that .assuming saves a bit of length and could, in some contexts, be a bit clearer. But I strongly suspect that I'm missing some other difference.

codesections
  • 8,900
  • 16
  • 50
  • 3
    "saves a bit of length", "bit clearer". Also: ❸ Encodes intent *statically*. In principle `.assume` could one day be a macro that does currying related optimization at compile-time that reliably beats what JIT can do. Maybe. ❹ Preserves original signature. If, for example, a parameter's name or type constraint is changed, the curried version of the routine will automatically track those changes, which may (often will be) for the best in terms of both compiler (Rakudo) enforcement of argument types/names and tooling (eg CommaIDE) display of signatures. – raiph Mar 14 '21 at 17:53
  • @raiph you very well may be correct that it's just those 4 differences. However, I wonder… the Code.assuming [source](https://github.com/rakudo/rakudo/blob/master/src/core.c/Code.pm6#L52-L324) is ~275 lines, which seems like a lot to spend on something that reduces down to a closure. I haven't yet dug into the source – though I will when I get some time if no one knows the answer – but it makes me wonder if there's more to it. – codesections Mar 14 '21 at 18:03
  • 3
    Presumably the code must do a custom, entirely manual introspected/metaprogrammed *replication of signature binding* **and** a custom, entirely manual metaprogrammed *construction of a new signature, and a new closure with that signature*. The binding emulation would need to step thru each parameter in the original signature, noting which `.assuming` args have been passed, which ones omitted, and which are `*` (and perhaps there's a `**` variant?). The construction is likely verbose too. And I imagine error messages might be myriad and/or complicated to construct in order to not be LTA. – raiph Mar 14 '21 at 19:16
  • 2
    `sub work($i, :$debug) { say $i if $debug; return $i }; my &w1 = &work.assuming(1); my &w2 = &w1.assuming(debug => True); w1(); w2();` When you start considering optional named arguments, chaining, etc currying becomes quite verbose compared to `assuming` – ugexe Mar 14 '21 at 22:48
  • 1
    "The current implementation of `.assuming` – used to curry or otherwise prime arguments for a routine – is not ideal, and an implementation that takes advantage of the argument rewriting capabilities of the new dispatcher would likely perform a great deal better" (from [Towards a new general dispatch mechanism in MoarVM](https://6guts.wordpress.com/2021/03/15/towards-a-new-general-dispatch-mechanism-in-moarvm/)) – raiph Mar 15 '21 at 16:34

1 Answers1

5

There really isn't a difference.

While the code to implement .assuming() is almost 300 lines, the important bit is only about ten lines of code.

    $f = EVAL sprintf(
        '{ my $res = (my proto __PRIMED_ANON (%s) { {*} });
           my multi __PRIMED_ANON (|%s(%s)) {
               my %%chash := %s.hash;
               $self(%s%s |{ %%ahash, %%chash });
           };
           $res }()',
        $primed_sig, $capwrap, $primed_sig, $capwrap,
        (flat @clist).join(", "),
        (@clist ?? ',' !! '')
    );

The rest of the code in .assuming is mostly about pulling information out of the signatures.


Let's take your code and insert it into that sprintf.
(Not exactly the same, but close enough for our purposes.)

{
  my $res = (
    #  $primed_sig          v----------------------v
    my proto __PRIMED_ANON ($first, $middle, $suffix) { {*} }
  );

  # $capwrap               vv
  # $primed_sig                v----------------------v
  my multi __PRIMED_ANON (|__ ($first, $middle, $suffix)) {
    # $capwrap   vv
    my %chash := __.hash;

    #     v---------------------------v  @clist
    $self(__[0], __[1], 'Public', __[2], |{ %ahash, %chash });
  };

  # return the proto
  $res
}()

If we simplify it, and tailor it to your code

my &surname-public = {
  my $res = (
    my proto __PRIMED_ANON ($first, $middle, $suffix) { {*} }
  );

  my multi __PRIMED_ANON ( $first, $middle, $suffix ) {
    longer-names( $first, $middle, 'Public', $suffix )
  };

  $res
}()

We can simplify it further by just using a pointy block.

my &surname-public = -> $first, $middle, $suffix {
  longer-names( $first, $middle, 'Public', $suffix )
};

Also by just using single letter parameter names.

my &surname-public = -> $a,$b,$c { longer-names($a, $b, 'Public', $c) }

Like I said, there really isn't a difference.


In the future, it may be more beneficial to use .assuming(). After it gets rewritten to use RakuAST.

Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
  • Very interesting, thanks. You mentioned that, "in the future, it may be more beneficial to use `.assuming()`. After it gets rewritten to use RakuAST." How about in the present? Seeing that `EVAL` call makes me think that using a pointy block would currently be _much_ faster than `assuming`, but it's possible that that's an unwarranted assumption. – codesections Mar 15 '21 at 14:20
  • @codesections The only benefit to using `.assuming()` currently, is that it is easy. (I'm sure that it is currently much slower.) With RakuAST it could cheat by not creating a block, for example. – Brad Gilbert Mar 15 '21 at 14:30
  • In very good timing, Johnathan's [latest 6guts post](https://6guts.wordpress.com/2021/03/15/towards-a-new-general-dispatch-mechanism-in-moarvm/) just confirmed your prediction (though based on the new dispatch mechanism rather than RakuAST): "The current implementation of assuming – used to curry or otherwise prime arguments for a routine – is not ideal, and an implementation that takes advantage of the argument rewriting capabilities of the new dispatcher would likely perform a great deal better". – codesections Mar 15 '21 at 15:04
  • @codesections at the present you are absolutely correct that most other methods are much faster. IME `assuming` implies a not-inconsequential performance hit (at least, the last time I used it and I know that no performance adjustments have been made, but there I was return subs generated by assuming, so I was feeling the maximum impact of the set up). – user0721090601 Mar 15 '21 at 16:11
  • 1
    Also, the simplest form to me is the implicit vars: `my &surname-public = { longer-name $^a, $^b, 'Public', $^c }` – user0721090601 Mar 15 '21 at 16:12
  • 1
    Oh, I like the version with formal/implicit vars a lot! And that one is significantly shorter/clearer than `.assuming`, so there really doesn't seem to be _any_ reason to use `.assuming` unless/until it's optimized enough to be significantly faster. – codesections Mar 15 '21 at 16:33
  • 1
    @codesections I'd say `.assuming` is significantly more succinct and readable in common cases such as when one is assuming just a single argument in the middle of many. – raiph Mar 16 '21 at 12:58
  • "I'd say .assuming is significantly more succinct and readable in common cases such as when one is assuming just a single argument in the middle of many." That's fair. Though I'd say that having _many_ positional arguments is generally a code smell, so I'm not sure that case actually is all that common. (And if the arguments are positional, you can pass a capture or a *% to pass the rest) – codesections Mar 16 '21 at 13:38