tags:

views:

34

answers:

2

I have a c function (dbread) that reads 'fields' from a 'database'. Most of those fields are single valued; but sometimes they are multi-valued. So I had c code that said

if valcount == 1
   return string
else
    make list
    foreach item in vals
        append to list
    return list

Because i thought most of the time people want a scalar.

However doing this leads to some odd parsing errors. Specifically if I want to add a value

set l [dbread x]            # get current c value
lappend l "extra value"     # add a value
dbwrite x {*}$l             # set it back to db

If x has single value and that value contains spaces the lappend parses wrong. I get a list with 3 items not 2. I see that this is because it is passed something that is not a list and it parses it to a list and sees 2 items.

set l "foo bar"
lappend l "next val" # string l is parsed into list -> [list foo bar]

so I end up with [list foo bar {next val}]

Anyway, the solution is to make dbread always return a list - even if there is only one item. My question is - is there any downside to this? Are there surprises lurking for the 90% case where people would expect a scalar

The alternative would be to do my own lappend that checks for llength == 1 and special cases it

+4  A: 

I think it's cleaner to have an API which always returns a list of results, be it one result or many. Then there's no special casing needed.

No downside, only upside.

Think about it, what if you move away from returning a single scalar and have a case in the future where you're returning a single value that happens to be a string with a space in it. If you didn't construct a list of that single value, you'd treat it as two values (because Tcl would shimmer the string into a list of two things). By always constructing a list of return values, all the code using your API will handle this correctly.

Just because Tcl doesn't have strict typing doesn't mean it's good style to return different types at different times.

Trey Jackson
+1: While I know of a way to do what the questioner asks (in 8.6) it is *not* something I want to tell people how to do! It's bad style! Always returning a list is *good* style.
Donal Fellows
so no gotchas that callers need to be aware of?
pm100
@pm100 I don't believe so, they just need to know that your API always returns a list of results.
Trey Jackson
A: 

One of the approaches I have taken in the past (when the data for each row could contain nulls or empty strings), was to use a list of lists of list:

{{a b} {c d}}  ;# two rows, each with two elements
{{{} b} {c d}} ;# two rows, first element of first row is null 
               ;# llength [lindex [lindex {{{} b} {c d}} 0] 0] -> 0
{ { {{}} b } { c d } } 
               ;# two rows, first element of first row is the empty string
               ;# llength [lindex [lindex {{{{}} b} {c d}} 0] 0] -> 1

It looks complicated, but it's really not if you treat the actual data items as an opaque data structure and add accessors to use it:

foreach row $db_result {
    foreach element $row {
        if {[db_isnull $element]} { 
            puts "null" 
        } elseif {![string length [db_value $element]]} {
            puts "empty string"
        } else {
            puts [db_value $element]
        }
    }
}

Admittedly, far more complicated than you're looking for, but I thought it worth mentioning.

RHSeeger