You're able to use things in the body of a function that aren't defined yet:
>> foo: func [x] [if cool-number? x [print "It's cool"]]
No error there, until you run the function.
But what if you use a datatype that's not defined yet? Here's Rebol2:
rebol2>> foo: func [x [cool-number!]] [print "Typechecked as cool!"]]
** Script Error: Invalid argument: cool-number!
rebol2>> cool-number!: integer!
rebol2>> foo: func [x [cool-number!]] [print "Typechecked as cool!"]
; no error
Red seems to just do a literal symbolic check, you can't define types under other words:
red>> cool-number: integer!
red>> foo: func [x [cool-number!]] [print "Typechecked as cool!"]
*** Script Error: cool-number! has no value
This Is A Problem For Extension Types
Let's say you have a collection of routines in a module, and one of them is able to operate on IMAGE!. But let's say you don't load the IMAGE! extension, and you don't use that routine.
The aggressive requirement that all types in type specs be resolvable at FUNC declaration time would prevent that module from loading.
It creates ordering problems... where you suddenly have to worry about the order you're loading extensions in, even when they aren't dependent on each other in order to load... but just because they can operate on each other's types. And it makes it impossible if they refer to each other's types.
Is The Answer To Wait Until The Function Is Called?
The historical problem with waiting was that the information in the spec was compacted and thrown away. But modern PARAMETER! works differently, and could handle it.
There are issues with type constraints changing out from under you, with regards to specialization and such. See this post, which surveys what happens when you do things like INTEGER!: TAG! in the middle of a run:
Survey of Redefining Datatype WORD!s
The answer there may be that have to lock any variables that are used as type constraints so you can't change them. ![]()
I'm not concerned about performance issues... it can be made to perform as well as it should.
The real question is just what to do about situations like:
>> foo: func [x [integer! askjdfljaslkdjfkakjsdhf]] [return x]
>> foo 10
== 10
Is that sane? You might say "no, that's obviously insane", but consider you could have just as easily written:
>> foo: func [x] [if integer? x [return x] askjdfljaslkdjfkakjsdhf]
>> foo 10
== 10
But taken to its logical conclusion, tolerating undefined things in type specs and just skipping them would also permit:
>> foo: func [x [askjdfljaslkdjfkakjsdhf integer!]] [return x]
>> foo 10
== 10
Okay, That Is Crazy
It seems there needs to be some concept of forward-declaration. Something that type specs tolerate as a non-match unless the thing loads.
It may be (and I'm still working on the finer details of this), that all antiform fences canonize to the same antiform fence for that word, which identifies a datatype. Meaning you could do a forward-declaration by just saying:
image!: ~{image!}~
Then when the actual IMAGE! library gets loaded (perhaps through a DLL and LOAD-EXTENSION) it agrees on that.
But even if that were able to work for the narrow case of a DATATYPE!, it doesn't help so much with functions. Let's say the datatypes were RGB-IMAGE! and RGBA-IMAGE! to distinguish having an alpha channel (for instance). Then there was ANY-IMAGE?. What if you want to use ANY-IMAGE? as a constraint?
Maybe this calls for a generalized "forward declaration" datatype? Something that is essentially unset, but tolerated by type specs?
Hey, Maybe That Already Exists... And It's Just Tolerance Of A Specific TRIPWIRE?
rgb-image!: ~<forward>~ ; hm, forward can mean a lot of things.
any-image?: ~<pending>~ ; "pending" sounds better. "I know it's coming"
rgba-image!: ~<unavailable>~ ; A "softer" unset state?
Well, it's one thought. If tripwires were immutable (they should be, but aren't today) then checking for a specific word in the tripwire can be made arbitrarily fast.
Setting things to this state might be done with something like:
extern [rgb-image! any-image? rgba-image!]
Something Like This Has To Be The Right Direction
I don't think the arbitrary-garbage-in-typespecs is an idea with a future.
One problem with tripwires, though, is that you get a difference between:
something?: ~<pending>~
foo: func [x [integer! something?]] [...]
and
something?: ~<pending>~
foo: func [x] [any [not integer? x not something? x] [fail "typecheck"] ...]
While the typecheck was willing to overlook something? being not defined, the explicit call is not willing to do that.
But you do have control there, to test to make sure the SOMETHING? isn't a tripwire before calling it.
foo: func [x] [
any [
not integer? x
(set? $something?) and (not something? x)
][
fail "typecheck"
]
...
]