<skip>-able Arguments


#1

A <skip> parameter is one where instead of getting a type error on a mismatch, it will set the parameter to null:

 foo: function [a :b [<skip> tag!] c] [
     dump [a b c]
 ]

 >> foo 1 2
 a: 1
 b: ;-- null
 c: 2

 >> foo 1 <hello> 2
 a: 1
 b: <hello>
 c: 2

You can only skip a "hard quoted" parameter. To understand why, imagine a having an evaluative skippable parameter followed by a quoted argument. If you evaluate the first argument--see it doesn't match the type--you can't really rewind time to then switch into a quoting mode for the second argument on the thing you already evaluated. It would be incoherent.

Beyond the technical reason you don't want <skip> on evaluated parameters, it also makes it clearer to the user. For instance, consider tagged compose:

>> compose <*> [(1 + 2) (<*> 1 + 2) (1 + 2)]
== [(1 + 2) 3 (1 + 2)]

You can tell at the source level that's tagged. But what if it said:

>> compose foo [(1 + 2) (<*> 1 + 2) (1 + 2)]
== ???

Does this mean that sometimes the block is taken by the compose and sometimes it's not? What if FOO is a block? The "shape" of the code changes, and that seems bad.

So it's for quoted arguments only...and that's probably for the best.

It's used by DEFAULT

The use in tagged compose is pretty obvious. But a trickier usage is DEFAULT, which quotes a SET-WORD! or SET-PATH! on its left to assign to it:

 >> x: null

 >> x: default [print "running" 1 + 2]
 running
 == 3 ;-- didn't have a value, so ran the body and assigned

 >> x: default [print "not run!" 10 + 20]
 == 3 ;-- already had a value, don't run the block and leave alone

The trick skippability brings to the table is that if there is no SET-WORD! or SET-PATH!, then it just runs the block. This makes it a nice tool in evaluative switch/case:

 >> case [
     1 > 2 [print "nope" 10]
     default [print "yep!" 20]
 ]
 yep
 == 20

CASE and SWITCH are written to just let the last value fall out of them.

 >> switch "not a number" [
      1 + 2 [...]
      3 + 4 [...]
      5 + 6
 ]
 == 11

So all DEFAULT does is fall back to acting like DO when it can't find a SET-WORD! or SET-PATH! to its left.

 >> switch "not a number" [
      1 + 2 [...]
      3 + 4 [...]
      default [5 + 6]
 ]
 == 11

It comes off as quite readable. (Though if you are lazy, or code golfing, you can just omit it...maybe some people will like that better.)

The implementation is now solid, so this feature is sticking around

The initial implementation was a bit creaky, especially when it came to skipping left hand side arguments. I was worried it might not be able to work efficiently--or maybe it wasn't able to work in a general way at all!

But things have firmed up very well. Impressively enough, this works:

>> foo: function [/a aa /b :bb [<skip> integer!]] [
    reduce [a (:aa else [<null>]) b (:bb else [<null>])]
]

>> foob: enfix :foo/b

>> parameters of :foob
== [bb /a aa]

>> 10 foob
== [_ <null> /b 10]

>> "not an integer!" foob
== [_ <null> _ <null>]

>> 10 -> foob/a 20
== [/a 20 /b 10]

And we can discuss what ("not an integer!" -> foob/a 20) should do, if that should be an error or not.

Anyway, FYI, that's a pretty wild case. If it can do that, it can probably work for whatever you want it to do. So start thinking of creative uses of <skip> in your own code...and if you do, post it in the thread here!