Rules about assigning SET-WORD have gotten stricter... only the SET-WORD at module scope are defined implicitly. Everything else has to be explicit. This means functions are much less likely to leak into global variables, and things are under much more control.
I was looking at @gchiu's midcentral and trying to update it to work under the new rules, and came across this:
parse demo [
(home: phone: mobile: email: null)
[opt whitespace]
surname: across to ","
thru space [opt whitespace]
[firstnames: across to "("] (trim:head:tail firstnames)
thru "(" title: across to ")" ; `title: between "(" ")"`
thru "BORN" dob: across to space
thru "(" age: across to ")" ; `age: into between "(" ")" integer!`
thru "GENDER" [opt whitespace] gender: across some alpha
thru "NHI" nhi: across nhi-rule
thru "Address" [opt whitespace] street: across to eol (?? street ?? 1)
thru some eol [opt whitespace] town: across to eol (?? 2 ?? town)
thru some eol [opt whitespace] city: across to eol (?? 3 ?? city)
[thru "Home" (?? 4)
| thru "Mobile" (?? 5)
| thru "EMAIL" (?? 50) [opt whitespace] email: across to space accept (okay)
| thru "Contact – No Known Contact Information" (?? 6) to <end> (print "Incomplete Demographics") accept (okay)
] [opt whitespace]
phone: across some digit (?? 51 ?? phone)
try [
thru some eol thru "Mobile" [opt whitespace] mobile: across some digit (?? 6 ?? mobile)
thru some eol try [thru "Email" [opt whitespace] email: across to space (?? 7 ?? email)]
]
to <end>
] except [
print "Could not parse demographic data"
return ~
]
That has problems, because things like surname: did not previously have definitions. (e.g. they were being written as globals, because the function this code lived in did not declare a <local> for surname.
If not obvious--it's good to get told about this kind of leakage! Because otherwise your function is potentially corrupting global data it's not supposed to.
(UPDATE: It turns out that (some) of these variables were actually intended as globals. They definitely should be grouped together in an object!)
The easiest thing to do would be to just make local definitions for all the fields. But that feels like you're repeating yourself.
"Hey, Maybe EMIT Could Be Used Here"...
I realized using EMIT to solve It would get a little more wordy, because it doesn't assume all SET-WORD are intended for the object. (This may mean that a CONSTRUCT combinator is needed that makes the assumption)
But this has a problem, when you want to give fields default values, in terms of how you stylize your code. Imagine you have some complex logic that decides if you want to emit fields--let's say more than one field are affected:
gather [
...
opt some [
blah blah [
emit foo: integer!
emit bar: block!
]
]
]
Now let's say you want foo to be null by default if the rule emitting it doesn't apply, and let's say you don't want bar at all in that case.
EMIT gives an error at the moment if you emit something that's already been emitted. It seems like it should (?) Though it could be that the last emit is the one that applies for the object. But that feels sketchy. Maybe there's something like an emit:update, refinement in order to say it's okay:
gather [
emit foo: (null)
opt some [
blah blah [
emit:update foo: integer!
emit bar: block!
]
]
]
Feels like too many colons.
It could be something like emit*:
gather [
emit foo: (null)
opt some [
blah blah [
emit* foo: integer!
emit bar: block!
]
]
]
Anyway, I'm not quite sure.
Making an OBJECT! does seem to be a good idea here, because the goal is to output the fields to a file:
write to file! unspaced ["/" nhi %.r] mold compose [
nhi: (nhi)
title: (title)
surname: (surname)
firstnames: (firstnames)
dob: (dob)
street: (street)
town: (town)
city: (city)
phone: (phone)
gender: (gender)
]
This stresses that a JSON-like output of objects is important, because that file should presumably contain a list (BLOCK! or a FENCE! {...}) and not something with make object! [...]