Multiple Return Values

This one’s for you Vincent. (But feel free to chime in Ethan)

  • It seems that the best way to elegantly and efficiently write a function to find a key in a hash table, dictionary, etc is to write a function with two return values. First value is whether or not the item was located, second value is the actual value for the key. Only problem I’m not sure that there are any cases where having a key in the hash table with the value of FALSE would be treated any differently then just not having the key in the table in the first place.
  • If we accept that there are functions that must return multiple values to be useful or efficient, [e.g. HashLookup or yuv2rgb()], then as far as I can see, you have three options:
    1. Language support for real multiple values returned from functions
    2. user kludged multiple values using list or a data structure
    3. language kludge it by using output variables passed by reference or mutable data structure to the function.
    4. I don’t like option 2 because it forces consing / memory allocation when it could potentially be avoided and option 3 requires mutable data structures or pass by reference, and those are not very Functional Programming friendly.

6 thoughts on “Multiple Return Values

  1. Not sure what your going for in the first bullet. I’m guess you mean finding a key and then using the key for a lookup. The other way to do it requiring no return values is to pass in a success and failure continuations. Confused by the FALSE key statement.

    I’m not a fan of multiple value functions. There’s too much boilerplate to be written to use them in places expecting single values. I want be able to use map on whatever the hell function I want.

    I see option 1 as a premature optimization and have no problem with option 2. A sufficiently smart compiler like ghc or mlton (and certainly stalin) will inline the function if the results are immediately destructured. Option 3 would be fine in a language with usage types. E.g. this reference must be set exactly once before use. The c# variant used in microsoft’s singularity os such a mechanism to ensure buffers are not modified after creation.

  2. My first thoughts,

    If objects are references to objects, as in Java (or Cocoa/ObjC where they are pointers), then dictionary lookup can just return ONE value, an object, with “not found” represented by NULL. In Haskell there is a maybe monad that encapsulates the same thing. Since I have been programming in Cocoa, I’m used to writing if([dictionary objectForKey:someKey]){} to check if a lookup succeeded. This seems very natural, although obviously using it has made me biased :-).

    So if complex values are allowed, I’m not sure I accept that there are functions that must return multiple values to be useful or efficient. I need to think on that more.

    I don’t like option 2 because it forces consing / memory allocation when it could potentially be avoided

    I recently saw an article showing “stream fusion — a new compiler optimization that removes intermediate data structures produced in function pipelines to produce very good code, yet which encourages a very high level coding style”. If a language supports that optimization, it looks like it solves the problem…

    Even without stream fusion, the trading an extra allocation for better readability seems worth it to me.

    I agree that option 3 isn’t great.

    I’m still on the fence regarding option 1. It seems like there is a tension between simplicity, and modeling a problem richly & explicitly. I need to look more into how different languages handle returning multiple values, and different functional languages deal with composing functions with many arguments…

  3. As far as using multiple value functions where one expects to use a single value function, Common Lisp handles that case well, IIRC, it just discards all but the first value. For example, the floor operator in CL returns two values… 1 – the value you probably want and 2 – wait it dropped…. You can still use floor in a map, it just uses the first value… (floor 3.5) returns 3.0 and .5, where as (map ‘list #’floor (list 3.5)) returns (3).

    As far as my hash example, yes I mean that the first value returned is success or failure and the second value is the actual mapped value.

    @V – The problem with if([dictionary objectForKey:someKey]){} as far as I was mentioning was that you don’t know whether the key was not in the dictionary or whether the value for the key was actually nil. But as I pointed out, I’m not sure when you would handle those cases differently.

  4. FWIW, if -objectForKey: returns nil, then there’s nothing in the dictionary under that key. There’s a sentinel value: [NSNull null] for representing nil in a Cocoa collection (you can’t put nil in any of them, including NSArray). I’m not a huge fan of sentinel values, but as you pointed out, it’s rare that the distinction between having nil or not having anything matters. I don’t think I’ve ever used NSNull in any of my code. There’s also an argument for having a -dictionaryContainsKey: method…

    The floor example is a bit scary to me. I don’t quite see the reason for making floor so complex. There are simple operations like “is this an integer?”, or “x – floor(x)” that give the same result more clearly… (cadr (floor x)) just doesn’t say “floor of x is equal to x” to me…

  5. FWIW, you wouldn’t say (cadr (floor x)). Floor returns multiple values, it DOES NOT return a list with multiple values. I don’t know that you are understanding multiple value returns completely.

    Also, given the way that multiple values work in CL, (equal? 3.0 (floor 3.5)) returns T. The second return value from floor is discard since it is not used.

    If you say (let ((foo (floor 3.6)) (print floor))) it will print out 3.0… If you want to capture the second value, you use use the multiple-value-bind construct, such as (multiple-value-bind (foo bar) (floor 3.5) …)

Leave a Reply

Your email address will not be published. Required fields are marked *