UCL: Some Updates
Made a few minor changes to UCL. Well, actually, I made one large change. I’ve renamed the foreach
builtin to for
.
I was originally planning to have a for
loop that worked much like other languages: you have a variable, a start value, and an end value, and you’d just iterate over the loop until you reach the end. I don’t know how this would’ve looked, but I imagined something like this:
for x 0 10 {
echo $x
}
# numbers 0..9 would be printed.
But this became redundant after adding the seq
builtin:
foreach (seq 10) { |x|
echo $x
}
This was in addition to all the other useful things you could do with the foreach loop1, such as loop over lists and hashes, and consume values from iterators. It’s already a pretty versatile loop. So I elected to go the Python way and just made it so that the for
loop is the loop to use to iterate over collections.
This left an opening for a loop that dealt with guards, so I also added the while
loop. Again, much like most languages, this loop would iterate over a block until the guard becomes false:
set x 0
while (lt $x 5) {
echo $x
set x (add $x 1)
}
echo "done"
Unlike the for
loop, this is unusable in a pipeline (well, unless it’s the first component). I was considering having the loop return the result of the guard when it terminates, but I realised that would be either false, nil, or anything else that was “falsy.” So I just have the loop return nil. That said, you can break from this loop, and if the break call had a value, that would be used as the result of the loop:
set x 0
while (lt $x 5) {
set x (add $x 1)
if (ge $x 3) {
break "Ahh"
}
} | echo " was the break"
The guard is optional, and if left out, the while
loop will iterate for ever.
The Set! Builtin
Many of these changes come from using of UCL for my job, and one thing I found myself doing recently is writing a bunch of migration scripts. This needed to get data from a database, which may or may not be present. If it’s not, I want the script to fail immediately so I can check my assumptions. This usually results in constructs like the following:
set planID (ls-plans | first { |p| eq $p "Plan Name" } | index ID)
if (not $planID) {
error "cannot find plan"
}
And yeah, adding the if block is fine — I do it all the time when writing Go — but it would be nice to assert this when you’re trying to set the variable, for no reason other than the fact that you’re thinking about nullability while writing the expression to fetch the data.
So one other change I made was add the set!
builtin. This will basically set the variable only if the expression is not nil. Otherwise, it will raise an error.
set! planID (ls-plans | first { |p| eq $p "Missing Plan" } | index ID)
# refusing to set! `planID` to nil value
This does mean that !
and ?
are now valid characters to appear in identifiers, just like Ruby. I haven’t decided whether I want to start following the Ruby convention of question marks indicating a predicate or bangs indicating a mutation. Not sure that’s going to work out now, given that the bang is being used here to assert non-nullability. In either case, could be useful in the future.
-
And the
seq
builtin ↩︎