For Better or Worse: MACRO

So it's long been theorized that there could exist a form of function that would splice its result into the feed of execution. And now we have it:

 appender: macro [x /ref] [
      if ref [
          return [append x second]  ; return block of "incomplete" code
      ] else [
          return [append x first]
      ]
 ]

 >> appender [a b c] [1 2 3]
 == [a b c 1]  ; acts as spliced `<<append x first>> [a b c] [1 2 3]`

 >> appender/ref [a b c] [1 2 3]
 == [a b c 2]  ; acts as spliced `<<append x second>> [a b c] [1 2 3]`

So you return a BLOCK! of what you want spliced in at the callsite. The bindings in that block won't be changed, however...so the x will still refer to the parameter value, not some x at the callsite.

The reason this exists now is to expose and test a core ability that is used by predicates to make them faster and more reliable. Predicates do a similar array splice...using the array underlying the PATH! that you pass it. This means it doesn't have to generate a function (that would need to be GC'd) or run a function application, so any .not.even? [2, 3] is aiming to have performance parity with any [not even? 2, not even? 3]

But as with most language features called "macro", there are downsides. The function interface for this macro looks like it only takes one argument, but it winds up implicitly picking up two. So macros are variadic. My plan for POINTFREE is to be smarter and actually generate an ACTION! that figures out how many parameters it needs to take, but that's more expensive to do and it was making predicates not as good as it should be.

Note that if you put an enfix operator at the beginning of a macro splice, it will not be able to see its left. So if you want a macro to see the left hand side parameter, the macro itself has to be enfix. It will see the enfix parameter, but won't "consume" it:

 add1020: enfix macro [discarded] [
     return [+ 1020]
 ]

 >> 304 add1020
 == 1324

As a general reminder of why you can't decide enfix "after the fact"... the concept of a single instruction "step" is one that has a finishing point. If all functions reserved the right to be enfix, then that would mean a DO of a BLOCK! couldn't be separated into individual evaluation steps...each function would run glued to the last. It would be possible to avoid this if COMMA!s between expressions were enforced so you called out where every expression ended, but we obviously do not want that!

3 Likes