Version 8 of Getting a value or a default value

Updated 2014-02-21 08:17:15 by aspect

Peter Lewerin (2013-12-26): I can't believe this basic trick hasn't been described here yet, but I can't find it even after repeated searches. I'll put it up here, and if it's already on the wiki somewhere, let me know and I'll nominate this page for deletion.

A common idiom in programming is "use this value unless it isn't what I want to have; in that case use this default value". For instance, when assigning a value to a variable, it can be useful to make sure that the variable gets some safe value instead if the intended value isn't up to snuff.

For me, it's often "I'd like to use $a unless it's an empty string, in which case I'll use $b". So I have this little proc which I tend to copy into my projects (as you can see, it's possible to have some value other than an empty string as the undesired value; actually I have never used that option except when testing the command):

proc either {value default {nullval {}}} {
    expr {$value ne $nullval ? $value : $default}
}

# use like this:

set foo {}
either $foo Tcl
# -> Tcl

set foo xxx
either $foo yyy
# -> xxx

either $foo yyy xxx
# -> yyy

A variant of this is to define the unwanted value as any logically false value (0, false, or no in Tcl): this works like the or operator (however it is written) in some languages (like Scheme's or or Perl's ||) but not as in other languages which either 1) don't accept non-boolean values as operands to or (Tcl doesn't), or 2) always has the or operator return a boolean value, or both.

proc eitherOr {value default} {
    expr {$value ni {0 "false" "no"} ? $value : $default}
}

set foo xxx
eitherOr $foo yyy
# -> xxx

set foo no
eitherOr $foo yyy
# -> yyy

Another variant is to instead of an unwanted value use however the language defines the undefined state. Some languages have operators (known as null coalescing operators) for this, such as C#'s ?? or Perl's //; Scheme's or works here too. Tcl doesn't have a value for undefined / null, but at least we can test a variable for existence:

proc eitherExists {varname default} {
    upvar $varname _varname
    expr {[info exists _varname] ? $_varname : $default}
}

set foo xxx
eitherExists foo yyy
# -> xxx

unset foo
eitherExists foo yyy
# -> yyy

Finally (?) we can use a provided predicate function to tell us whether to use the value or the default value. Note that the function must be an actual predicate, i.e. return a legal boolean value (1, true, yes, 0, false, or no).

proc eitherApply {value default func} {
    expr {[apply $func $value] ? $value : $default}
}

set foo xxx
eitherApply $foo yyy {{val} {string match x* $val}}
# -> xxx

set foo aaa
eitherApply $foo yyy {{val} {string match x* $val}}
# -> yyy

This is of course the most generic variant. Actually it can be used to redefine the original command:

proc either {value default {func {{val} {expr {$val ne {}}}}}} {
    expr {[apply $func $value] ? $value : $default}
}

set foo {}
either $foo Tcl
# -> Tcl

set foo xxx
either $foo yyy
# -> xxx

either $foo yyy {{val} {expr {$val ne "xxx"}}}
# -> yyy

aspect: thanks for refreshing this - I love the name [either] and I'm definitely stealing that! Not so sure about the subcommands .. they might be a good case for sprucing up with options and arguments. A similar pattern I've been using a lot lately but irritatingly can't find a good name for is:

proc ifnotempty {_var args} {
    upvar 1 $_var var
    if {[info exists var] && $var ne ""} {
        uplevel 1 {*}$args
    }
}

I've considered [maybe], [iff] (which might be more like an error-suppressing if) .. but nothing quite fits.

PL: the term iff is usually understood as meaning "if and only if", so I wouldn't use that. Maybe ifhas?

aspect: Oops - I mis-copied the code. With the {*} hopefully it makes more sense, and makes the comment I deleted redundant.


AMG: As I wrote on the [append] page, [append] can be used to set a variable to empty string if it doesn't already exist, leaving its value alone otherwise. This is often all that's needed. Just do:

append var ""

As a bonus, this returns the variable's value (or empty string if it didn't exist), so just write [append var ""] instead of $var or [set var].

PL: that would indeed equivalent to using eitherExists above with the invocation eitherExists var "". It doesn't handle any of the other cases, though.