Fun with NULL: Refinement Revocation + "BLANK!-in-NULL-out"


#1

A feature that doesn’t have a lot of uses in the wild yet is “refinement revocation”. While updating the %httpd.reb script, I noticed an interesting place it could be used.

The code needed some updating, because it was assigning the result of a conditional (which might fail) directly through a SET-WORD!..which could be null, and thus not legal:

type: if string? headers/Content-Type [
    copy/part type: headers/Content-Type any [
        find type ";"
        tail type
    ]
]

I could have just changed that to try if string? (and update it to try if text? while I was in the area). But I looked at it a little closer.

What’s going on is that Content-Type contains a string which may have a semicolon in it, and if so the content is wanted only up to the semicolon but not including it.

But the ANY isn’t really needed. It’s supplying the argument to COPY’s /PART refinement, saying that the COPY should go to the tail if there was no semicolon. But since FIND returns NULL if it doesn’t find anything, that would signal refinement revocation…meaning that the /PART will be taken back. You can just say:

 copy/part type: headers/Content-Type find type ";"

If find type ";" comes back as NULL that effectively becomes just copy type: headers/Content-Type. This is an intrinsic feature (and is why you can’t have an <opt> refinement arg…such arguments are NULL if-and-only-if the refinement is not in use)

Should you want to document that you were aware the FIND could fail–and you’re not doing the revocation on accident–you could throw in an OPT. It won’t do anything, since FIND never returns BLANK! to be turned into NULL. But it will show that you knowingly generated NULLs there.

So one way it might be written is:

type: try all [
    text? type: headers/Content-Type
    copy/part type (opt find type ";")
]

But there’s a lot of options here, like using PARSE. PARSE now speaks the “blank in, NULL out” protocol, so you can use that to your advantage:

parse try match text! headers/Content-Type [
    copy type: to [";" | end]
] else [
    type: _
]

Here MATCH TEXT! will give back a null if it’s not, then TRY will convert the NULL to a BLANK!, which PARSE will convert back to a NULL…triggering the ELSE.

Here’s something that’s not necessarily clear, but interesting, is to use that FIND and COPY participate in the “blank in, null out” policy too. So you could say:

type: try match text! headers/Content-Type
type: maybe copy/part type (opt find type ";")

If headers/Content-Type isn’t a TEXT!, then MATCH will give NULL, and TRY will turn that into a BLANK! so it can be assigned to type. Then on the next line, if TYPE is blank, both FIND and COPY will return NULL. So the MAYBE will get NULL on the right hand side, and avoid overwriting the existing value in TYPE: on the left.