Making ARROW Functions Cheaper

To recap:

eval code except e -> [...]  ; method A

; ...vs...

eval code except (e -> [...])  ; method B

The technical trickery that pulls off [A] has been a showpiece, though also a thorn.

And while the arrow function generator is much lighter than LAMBDA... it's not free.

So [B] will pretty much be cheaper no matter what.

IT MIGHT BE that if the BLOCK! you pass in is at the HEAD, it could make an ACTION! that was a special format... basically just a BLOCK! cell which slipstreamed a Symbol* into where the index in a block cell would go. This would be a way of keeping the block in suspended animation until you were sure you needed it.

The problem would be that if you don't create an identity then every arrow function would have to come into existence as a function independently:

>> foo: a -> [a]

>> ^bar: ^foo

; no calls made yet, and you have 2 copies of the arrow Cell

>> help bar  ; would incarnate an action identity to answer questions.
...

>> foo/ = bar/
== \~null~\  ; separate function identiites

Creating an identity is all you need to do, and that costs 8 platform pointers, but you can just put the "spec" and the "body" into that as 2 Cells (a Pairing), and then when the function machinery notices something is a pairing in an ACTION! Cell it can get around to "incarnating it" at the moment it's asked to perform as an action, at which point it would morph the Pairing into an ordinary ACTION! Phase Stub.*

No Intrinsics Unless They're Arity 1

In terms of speeding up the creation, it would be a significant benefit -> could be intrinsic.

But you couldn't make them with an intrinsic unless they were arity-1 with some syntax or another...

>> -> [e : print mold e]
== &[arrow! [e : print mold e]]

At which point they really would cost "nothing" (same as the BLOCK! Cell, which already exists...just with a different type byte).

That's a non-trivial performance boost--intrinsic dispatch and no allocations.

But aaargh, it's a loss...

y: case [
    1 > 2 [<no>]
    1 < 2 [<yes>]
] then -> [x :
    assert [x = <yes>]
    1000 + 20
])
assert [y = 1020]

It's just not as nice as:

y: case [
    1 > 2 [<no>]
    1 < 2 [<yes>]
] then x -> [
    assert [x = <yes>]
    1000 + 20
]
assert [y = 1020]

Or even if we had to put the group:

y: case [
    1 > 2 [<no>]
    1 < 2 [<yes>]
] then (x -> [
    assert [x = <yes>]
    1000 + 20
])
assert [y = 1020]

I don't know what crazy syntaxes we might pull out. One could use a CHAIN!

y: case [
    1 > 2 [<no>]
    1 < 2 [<yes>]
] then x:[
    assert [x = <yes>]
    1000 + 20
])
assert [y = 1020]
y: case [
    1 > 2 [<no>]
    1 < 2 [<yes>]
] then (x):[
    assert [x = <yes>]
    1000 + 20
])
assert [y = 1020]

I Think Identity Creation Is Essential

So we can't really skip that.

But I think it can be cheap enough such that bypassing the GROUP! eval to suppress the evaluation comes out as a net win.

Waitaminute

:hand_with_fingers_splayed:

Infix arity-2 functions can be Intrinsic because they can receive their left hand side in the OUT Cell of their Parent "Level".

The previous step's output is already sitting there. Normal dispatch copies it into a frame Cell. But for an intrinsic (which has no frame, hence no frame cells) you could just leave it in the parent Level's OUT Cell. Then with an evaluation into the parent's temp workspace Cell, you have a place for both the arguments to live.

  • Make 8-platform-pointer Stub
  • Copy OUT Cell into fist 4-platform-pointer position
  • Copy SCRATCH Cell into second 4-platform pointer position
  • Overwrite OUT with a special ACTION! value
  • Special ACTION! doesn't become "real" unless you try to run it

There may be ways of dodging making the action "real" even if it runs. But at some point, someone's going to reflect the stack or ask how many arguments it has and reifying the action will have some cost.

Or Maybe I'm Missing Something?

I'll have to look into it. There might be something that kills this idea.

But if not...then you should be able to make any Arity-2 function intrinsic using the same argument. It would have to be fiddly since its workspace Cell and Out cell were both in use, but it's possible most of the time, I think.

I've decided this will be mandatory, because branches have to be taken literally in all cases.

But that gives us one weird possibility... if you write this exact pattern of WORD! then -> then BLOCK! (or FENCE!), the branch code can optimize it, to just bind the code to a variable with that name and value and run it.

You'd need to be sure that -> was bound to the lib version of the arrow function, and you weren't in a stepwise debugging situation.

But this would bypass the group evaluation, and bypass the action! generation. Nearly as cheap as running a plain block!

Another Pattern: Dialected Function Calls

What if you told the branching structure what you wanted to call the variable, via the CHAIN! ?

y: case [
    1 > 2 [<no>]
    1 < 2 [<yes>]
] then:$x [
    assert [x = <yes>]
    1000 + 20
]
assert [y = 1020]

It would mean all your branches would have to share the same name... but this might be a great feature!

>> case:all:$x [
       1000 + 20 [print ["x is" x]]
       300 + 4 [print ["x is" x]]
   ]
x is 1020
x is 304

:thinking:

That's pretty badass. Worth thinking about.

1 Like