Arrow Function -> Written In Usermode

I thought it would be interesting to implement the "unpacking mode" of the arrow function generator in usermode:

>> packsum: ~(a b)~ -> [a + b]

>> packsum pack [1000 20]
== 1020

But how would one go about building such a thing on top of the basic function abstraction?

Here's at least one way:

[->]: lambda [
    []: [action!]
    @(spec) "Ordinary spec, single arg spec, or ~(args to unpack)~"
        [<hole> _ word! 'word! ^word! :word! block! ~group!~]
    @(body) "Code to execute (will not be deep copied)"
        [block! fence!]
][
    either quasiform? ^spec [
        spec: as block! unquasi spec
        lambda [^pack [pack!]] (compose ${
            f: copy '(lambda spec (body))
            set words of f ^pack
            eval f
        })
    ][
        lambda blockify any [spec '?] (body)
    ]
]

There's A Lot Going On, Let's Break It Down

The only quasiform we typecheck in the spec is a quasi-group. So if we see QUASIFORM? we know it's like ~(a b)~.

So the first thing we do is turn it into a plain BLOCK!:

spec: as block! unquasi spec  ; makes [a b]

Then we start making a function...which is the function we will return, that takes a single argument that is a PACK!.

lambda [^pack [pack!]] (...)

This function we are creating is going to call a function which takes A and B as ordinary parameters. So uses that adjusted spec to make such a function:

lambda spec (body)   ; e.g. `lambda [a b] [a + b]`

But we do something interesting... that function isn't put into a variable, but we make it live inside the body of the single-arity function we are composing:

lambda [^pack [pack!]] (compose ${
    f: copy '(lambda spec (body))  ; <-- FRAME! *lives here*
    ...
})

Yep, that quoted FRAME! is embedded right into the body. (If we don't quote it, the frame would execute when encountered by the evaluator...but quoting it means it evaluates as-is to a frame.)

And so all we have to do to get a "scriptable interface" for calling that function is copy that frame. Now we've got something that you can assign f.a and f.b on the interface of.

But we don't want to re-implement all the logic of PACK! assignment. We want what happens to be the same thing as if we said [f.a f.b]: ^pack ... e.g. unsetting extra elements if the pack contains too few items. That functionality is available to us through SET of a BLOCK!.

We could use our spec block. But that's not bound into the frame, and also it may have more information in it than we want (type checking, perhaps?). We want just the words of the frame, with binding into the frame... words of gives us that.

So the code kind of winds up equivalent to the comments here:

f: copy '(lambda spec (body))   ; f: make frame! lambda [a b] [a + b]
set words of f ^pack            ; set inside f '[a b] ^pack
eval f                          ; eval f

A Step Closer To The Minecraft-of-Programming

Surprising generality arising from such relative simplicity. I mentioned getting typechecking in the quasi-spec for your arguments:

>> typepacksum: ~(a [integer!] b [integer!])~ -> [a + b]

>> typepacksum pack [1000 20]
== 1020

>> typepacksum pack [10.20 3.04]
** PANIC: ~null~ got ~{decimal!}~ for a argument, expected [integer!]

The error message could use some work there. It's hard to say what to do when a generative step doesn't have a name.

But still, pretty far out, huh?

And the idea that it degrades even when your pack has fewer elements:

>> reflector: ~(^a ^b ^c)~ -> [reduce [lift ^a lift ^b lift ^c]]

>> reflector pack [1 2 3]
== ['1 '2 '3]

>> reflector pack [1 2]
== ['1 '2 ~,~]

>> reflector pack []
== [~,~ ~,~ ~,~]

I threw in another idea I've had recently of what may be the best use for ? in the language, which is to be the name of an anonymous parameter to functions:

>> foo: (-> [? + 4])

>> foo 300
== 304

This is based on my feeling that ? doesn't really look like it operates on anything. It looks like a placeholder, very standalone. If I look at [a ? b] it doesn't really register to me that the ? applies more to either A or to B. If anything, it's postfix/infix. But I kind of think it fits better as an independent thing in its own right.

1 Like

This got a little nicer due to the graceful decay of ACTION! (an unstable antiform now) to FRAME!, in a non-assignment context.

Previously, if you wanted to avoid an error here, you'd have had to use UNRUN to turn the stable antiform into a FRAME!:

 lambda [^pack [pack!]] (compose ${
    f: copy '(unrun lambda spec (body))
    ...
 })

You might wonder why you aren't allowed to just say as block! spec.

I'm kind of reticent to allow functions like AS BLOCK! to just disregard quoted and quasiform status of things.

>> as block! first [~(a b)~]
** PANIC: AS doesn't work on QUOTED! or QUASIFORM! directly

The premise of this is that if code becomes too flippant about decorations being thrown away, then you wind up with a bunch of code that acts the same whether you give it (a b) or ''(a b) or '''''~(a b)~.

So I've been deliberately trying to make most of the system to choke when you give it decorated values. It does make this seem a bit more belabored than it needs to be, but I think this is the right call.

With the system becoming tolerant of x: () as a way to unassign variables, without having to write ^x: (), I've come to wonder if untyped variables should accept unset states.

reflector: ~(a b c)~ -> [reduce [lift ^a lift ^b lift ^c]]

I'm open to it.