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.