71

My primary language is statically typed (Java). In Java, you have to return a single type from every method. For example, you can't have a method that conditionally returns a String or conditionally returns an Integer. But in JavaScript, for example, this is very possible.

In a statically typed language I get why this is a bad idea. If every method returned Object (the common parent all classes inherit from) then you and the compiler have no idea what you're dealing with. You'll have to discover all your mistakes at run time.

But in a dynamically typed language, there may not even be a compiler. In a dynamically typed language, it's not obvious to me why a function that returns multiple types is a bad idea. My background in static languages makes me avoid writing such functions, but I fear I'm being close minded about a feature that could make my code cleaner in ways I can't see.


Edit: I'm going to remove my example (until I can think of a better one). I think it's steering people to reply to a point I am not trying to make.

10
  • Why not throw an exception in the error case?
    – TrueWill
    Jan 27, 2014 at 19:12
  • 1
    @TrueWill I address that in the next sentence. Jan 27, 2014 at 19:14
  • Have you noticed that this is a common practice in more dynamic languages? It's common in functional languages, but there the rules are made very explicit. And I know that it's common, especially in JavaScript, to allow arguments to be different types (as that's essentially the only way to do function overloading), but I've rarely seen this applied to return values. Only example I can think of is PowerShell with its mostly-automatic array wrapping/unwrapping everywhere, and scripting languages are a bit of an exceptional case.
    – Aaronaught
    Jan 28, 2014 at 4:05
  • 3
    Not mentioned, but there are many examples (even in Java Generics) of functions that take a return-type as a parameter; e.g. in Common Lisp we have (coerce var 'string) yields a string or (concatenate 'string this that the-other-thing) likewise. I've written things like ThingLoader.getThingById (Class<extends FindableThing> klass, long id) as well. And, there, I might only return something that subclasses what you asked for: loader.getThingById (SubclassA.class, 14) might return a SubclassB that extends SubclassA ...
    – BRPocock
    Jan 28, 2014 at 4:22
  • 1
    Dynamically typed languages are like the Spoon in the movie The Matrix. Do not try to define the Spoon as a string or a number. That would be impossible. Instead... only try to understand the truth. That there is no Spoon.
    – Reactgular
    Jan 28, 2014 at 16:50

14 Answers 14

43

By contrast to other answers, there are cases where returning different types is acceptable.

Example 1

sum(2, 3) → int
sum(2.1, 3.7) → float

In some statically typed languages, this involves overloads, so we can consider that there several methods, each one returning the predefined, fixed type. In dynamic languages, this may be the same function, implemented as:

var sum = function (a, b) {
    return a + b;
};

Same function, different types of return value.

Example 2

Imagine you get a response from an OpenID/OAuth component. Some OpenID/OAuth providers may contain more information, such as the age of the person.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Facebook',
//     name: {
//         firstName: 'Hello',
//         secondName: 'World',
//     },
//     email: '[email protected]',
//     age: 27
// }

Others would have the minimum, would it be an email address or the pseudonym.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Google',
//     email: '[email protected]'
// }

Again, same function, different results.

Here, the benefit of returning different types is especially important in a context where you don't care about types and interfaces, but what objects actually contain. For example, let's imagine a website contains mature language. Then the findCurrent() may be used like this:

var user = authProvider.findCurrent();
if (user.age || 0 >= 16) {
    // The person can stand mature language.
    allowShowingContent();
} else if (user.age) {
    // OpenID/OAuth gave the age, but the person appears too young to see the content.
    showParentalAdvisoryRequestedMessage();
} else {
    // OpenID/OAuth won't tell the age of the person. Ask the user himself.
    askForAge();
}

Refactoring this into code where every provider will have its own function which will return a well-defined, fixed type would not only degrade the code base and cause code duplication, but also will not bring any benefit. One may end up doing horrors like:

var age;
if (['Facebook', 'Yahoo', 'Blogger', 'LiveJournal'].contains(user.provider)) {
    age = user.age;
}
15
  • 5
    "In statically typed languages, this involves overloads" I think you meant "in some statically typed languages" :) Good statically typed languages don't require overloading for something like your sum example.
    – Andres F.
    Jan 27, 2014 at 19:07
  • 17
    For your specific example, consider Haskell: sum :: Num a => [a] -> a. You can sum a list of anything that is a number. Unlike javascript, if you attempt to sum something that is not a number, the error will be caught at compile time.
    – Andres F.
    Jan 27, 2014 at 19:12
  • 3
    @MainMa In Scala, Iterator[A] has method def sum[B >: A](implicit num: Numeric[B]): B, which again allows to sum any kind of numbers and is checked at compile time.
    – Petr
    Jan 28, 2014 at 6:12
  • 3
    @Bakuriu Of course you can write such a function, though you'd first have to either overload + for strings (by implementing Num, which is a terrible idea design-wise but legal) or invent a different operator/function that's overloaded by integers and strings respectively. Haskell has ad-hoc polymorphism via type classes. A list containing both integers and strings is much harder (possibly impossible without language extensions) but a rather different matter.
    – user7043
    Jan 28, 2014 at 15:10
  • 2
    I'm not particularly impressed with your second example. Those two objects are the same conceptual "type", it's just that in some instances certain fields are not defined or populated. You could represent that just fine even in a statically-typed language with null values.
    – Aaronaught
    Feb 6, 2014 at 16:55
33

In general, it's a bad idea for the same reasons the moral equivalent in a statically typed language is a bad idea: You have no idea which concrete type is returned, so you have no idea what you can do with the result (aside from the few things that can be done with any value). In a static type system, you have compiler-checked annotations of return types and such, but in a dynamic language the same knowledge still exists - it's just informal and stored in brains and documentation rather than in source code.

In many cases, however, there is rhyme and reason to which type is returned and the effect is similar to overloading or parametric polymorphism in static type systems. In other words, the result type is predictable, it's just not quite as simple to express.

But note that there may be other reasons a specific function is badly designed: For example, a sum function returning false on invalid inputs is a bad idea primarily because that return value is useless and error-prone (0 <-> false confusion).

10
  • 41
    My two cents: I hate it when that happens. I've seen JS libraries that return null when there are no results, an Object on one result, and an Array on two or more results. So, instead of just looping through an array, you're forced to determine if it's null, and if it isn't, if it's an array, and if it is, do one thing, otherwise do something else. Especially since, in the normal sense, any sane developer is going to just add the Object to an Array and recycle the program logic from handling an array.
    – phyrfox
    Jan 27, 2014 at 22:57
  • 10
    @Izkata: I don't think NaN is "counterpoint" at all. NaN is actually a valid input for any floating-point calculation. You can do anything with NaN that you could do with an actual number. True, the end result of said calculation may not be very useful to you, but it won't lead to strange runtime errors and you don't need to check for it all the time - you can just check once at the end of a series of calculations. NaN isn't a different type, it's just a special value, sort of like a Null Object.
    – Aaronaught
    Jan 28, 2014 at 3:50
  • 5
    @Aaronaught On the other hand, NaN, like the null object, tends to hide when errors occur, only for them to pop up later. For example, your program is likely to blow up if NaN slips into a loop conditional.
    – Izkata
    Jan 28, 2014 at 4:57
  • 2
    @Izkata: NaN and null have totally different behaviours. A null reference will blow up immediately upon access, a NaN propagates. Why the latter happens is obvious, it allows you to write mathematical expressions without having to check the result of every sub-expression. Personally I see null as far more damaging, because NaN is representing a proper numerical concept that you can't get away without and, mathematically, propagating is the correct behavior.
    – Phoshi
    Jan 28, 2014 at 9:27
  • 5
    I don't know why this turned into a "null vs. everything else" argument. I was merely pointing out that the NaN value is (a) not actually a different return type and (b) has well-defined floating-point semantics, and therefore really doesn't qualify as an analogue to a variably-typed return value. It's not all that different from zero in integer arithmetic, really; if a zero "accidentally" slips into your calculations then you're often likely to end up with either zero as a result or a divide-by-zero error. Are the "infinity" values defined by IEEE floating point also evil?
    – Aaronaught
    Jan 28, 2014 at 13:47
26

In dynamic languages, you shouldn't ask whether return different types but objects with different APIs. As most dynamic languages don't really care about types, but use various versions of duck typing.

When returning different types make sense

For example this method makes sense:

def load_file(file): 
    if something: 
       return ['a ', 'list', 'of', 'strings'] 
    return open(file, 'r')

Because both file, and a list of strings are (in Python) iterables that return string. Very different types, the same API (unless someone tries to call file methods on a list, but this is different story).

You can conditionally return list or tuple (tuple is an immutable list in python).

Formally even doing:

def do_something():
    if ...: 
        return None
    return something_else

or:

function do_something(){
   if (...) return null; 
   return sth;
}

returns different types, as both python None and Javascript null are types on their own right.

All these use cases would have their counterpart in static languages, function would just return a proper interface.

When returning objects with different API conditionally is a good idea

As to whether returning different APIs is a good idea, IMO in most cases it doesn't make sense. Only sensible example that comes to mind is something close to what @MainMa said: when your API can provide varying amount of detail it might make sense to return more detail when available.

7
  • 4
    Good answer. Worth noting that the Java / statically typed equivalent is to have a function return an interface rather than a specific concrete type, with the interface representing the API / abstraction.
    – mikera
    Jan 28, 2014 at 0:30
  • 1
    I think you are nitpicking, in Python a list and a tuple may be of different "types", but they are of the same "duck type", i.e. they support the same set of operations. And cases like this are exactly why duck typing has been introduced. You can do long x = getInt() in Java too.
    – Kaerber
    Jan 28, 2014 at 8:23
  • “Returning different types” means “returning objects with different APIs”. (Some languages make the way the object is constructed a fundamental part of the API, some don't; that's a matter of how expressive the type system is.) In your list/file example, do_file returns an iterable of strings either way. Jan 28, 2014 at 11:28
  • 1
    In that Python example, you could also call iter() on both the list and the file to make sure the result can only be used as an iterator in both cases. Jan 28, 2014 at 12:53
  • 1
    returning None or something is a killer for performance optimization done by tools like PyPy, Numba, Pyston and others. This is one of the Python-ism that make python not-so-fast.
    – Matt
    Dec 8, 2014 at 13:13
9

Your question makes me want to cry a little. Not for the example usage you provided, but because someone will unwittingly take this approach too far. It's a short step away from ridiculously unmaintainable code.

The error condition use case kind of makes sense, and the null pattern (everything has to be a pattern) in statically typed languages does the same type of thing. Your function call returns an object or it returns null.

But it's a short step to saying "I'm going to use this to create a factory pattern" and return either foo or bar or baz depending upon the function's mood. Debugging this will become a nightmare when the caller expects foo but was given bar.

So I don't think you're being closed minded. You're being appropriately cautious in how use the language's features.

Disclosure: my background is in statically typed languages, and I have generally worked on larger, diverse teams where the need for maintainable code was fairly high. So my perspective is probably skewed as well.

6

Using Generics in Java allows you to return a different type, while still retaining static type safety. You simply specify the type that you want to return in the generic type parameter of the function call.

Whether you could use a similar approach in Javascript is, of course, an open question. Since Javascript is a dynamically typed language, returning an object seems like the obvious choice.

If you want to know where a dynamic return scenario might work when you are used to working in statically-typed language, consider looking at the dynamic keyword in C#. Rob Conery was able to successfully write an Object-Relational Mapper in 400 lines of code using the dynamic keyword.

Of course, all dynamic really does is wrap an object variable with some runtime type safety.

4

It's a bad idea, I think, to return different types conditionally. One of the ways this comes up frequently for me is if the function can return one or more values. If only one value needs to be returned, it may seem reasonable to just return the value, rather than packing it in an Array, to avoid having to unpack it in the calling function. However, this (and most other instances of this) places an obligation on the caller to distinguish between and handle both types. The function will be easier to reason about if it's always returning the same type.

4

The "bad practice" exists regardless of whether your language is statically typed. The static language does more to steer you away from these practices, and you might find more users who complain about "bad practice" in a static language because it's a more formal language. However, the underlying problems are there in a dynamic language, and you can determine whether they're justified.

Here is the objectionable part of what you propose. If I don't know what type is returned, then I can't use the return value immediately. I have to "discover" something about it.

total = sum_of_array([20, 30, 'q', 50])
if (type_of(total) == Boolean) {
  display_error(...)
} else {
  record_number(total)
}

Often this kind of switch in code is simply bad practice. It makes the code harder to read. In this example, you see why throwing and catching exception cases is popular. Put another way: if your function cannot do what it says it does, it should not return successfully. If I call your function, I want to do this:

total = sum_of_array([20, 30, 'q', 50])
display_number(total)

Because the first line returns successfully, I assume that total actually contains the sum of the array. If it doesn't return successfully, we jump to some other page of my program.

Let's use another example that's not just about propagating errors. Maybe sum_of_array tries to be smart and return a human-readable string in some cases, like "That's my locker combination!" if and only if the array is [11,7,19]. I'm having trouble thinking of a good example. Anyway, the same problem applies. You have to inspect the return value before you can do anything with it:

total = sum_of_array([20, 30, 40, 50])
if (type_of(total) == String) {
  write_message(total)
} else {
  record_number(total)
}

You might argue that it would be useful for the function to return an integer or a float, e.g.:

sum_of_array(20, 30, 40) -> int
sum_of_array(23.45, 45.67, 67.789044) -> float

But those results aren't different types, as far as you're concerned. You're going to treat them both as numbers, and that's all you care about. So sum_of_array returns the number type. This is what polymorphism is about.

So there are some practices you might be violating if your function can return multiple types. Knowing them will help you determine if your particular function should return multiple types anyway.

1
  • Sorry, I led you astray with my poor example. Forget I mentioned it in the first place. Your first paragraph is on point. I also like your second example. Jan 27, 2014 at 23:06
4

Actually, it's not very uncommon at all to return different types even in a statically typed language. That's why we have union types, for example.

In fact, methods in Java almost always return one of four types: some kind of object or null or an exception or they never return at all.

In many languages, error conditions are modeled as subroutines returning either a result type or an error type. For example, in Scala:

def transferMoney(amount: Decimal): Either[String, Decimal]

This is a stupid example, of course. The return type means "either return a string or a decimal". By convention, the left type is the error type (in this case, a string with the error message) and the right type is the result type.

This is similar to exceptions, except for the fact that exceptions also are control flow constructs. They are, in fact, equivalent in expressive power to GOTO.

4
  • method in Java returning exception sounds weird. "Return an exception"... hmm
    – gnat
    Jan 28, 2014 at 15:55
  • 3
    An exception is one possible result of a method in Java. Actually, I forgot a fourth type: Unit (a method isn't required to return at all). True, you don't actually use the return keyword, but it's a result that comes back from the method nonetheless. With checked exceptions, it is even explicitly mentioned in the type signature. Jan 28, 2014 at 15:58
  • I see. Your point seem to have some merits, but the way it's presented doesn't make it look compelling... rather opposite
    – gnat
    Jan 28, 2014 at 16:01
  • 4
    In many languages, error conditions are modeled as a subroutine returning either a result type or an error type. Exceptions are a similar idea, except they are also control flow constructs (equal in expressive power to GOTO, actually). Jan 28, 2014 at 16:09
4

No answer has yet mentioned SOLID principles. In particular, you should follow the Liskov substitution principle, that any class receiving a type other than the expected type can still work with whatever they get, without doing anything to test what type is returned.

So, if you throw some extra properties onto an object, or wrap a returned function with some kind of decorator that still accomplishes what the original function was intended to accomplish, you are good, as long as no code calling your function ever relies on this behavior, in any code path.

Instead of returning a string or an integer, a better example might be it returns a sprinkler system or a cat. This is fine if all the calling code is going to do is call functionInQuestion.hiss(). Effectively you have an implicit interface that the calling code is expecting, and a dynamically typed language won't force you to make the interface explicit.

Sadly, your co-workers probably will, so you probably have to do the same work anyway in your documentation, except there isn't a universally accepted, terse, machine-analyzable way to do it - as there is when you define an interface in a language that has them.

3

The one place where I see myself sending different types is for invalid input or "poor mans exceptions" where the "exceptional" condition isn't very exceptional. For example, from my repository of PHP utility functions this abridged example:

function ensure_fields($consideration)
{
        $args = func_get_args();
        foreach ( $args as $a ) {
                if ( !is_string($a) ) {
                        return NULL;
                }
                if ( !isset($consideration[$a]) || $consideration[$a]=='' ) {
                        return FALSE;
                }
        }

        return TRUE;
}

The function nominally returns a BOOLEAN, however it returns NULL on invalid input. Note that since PHP 5.3, all internal PHP functions behave this way as well. Additionally, some internal PHP functions return FALSE or INT on nominal input, see:

strpos('Hello', 'e');  // Returns INT(1)
strpos('Hello', 'q');  // Returns BOOL(FALSE)
8
  • 4
    And this is why I hate PHP. Well, one of the reasons, anyway.
    – Aaronaught
    Jan 28, 2014 at 13:56
  • 1
    A downvote because someone doesn't like the language that I develop in professionally?!? Wait until they find out that I'm Jewish! :)
    – dotancohen
    Jan 28, 2014 at 14:59
  • 4
    Don't always assume that the people who comment and the people who downvote are the same people.
    – Aaronaught
    Jan 28, 2014 at 15:06
  • 1
    I prefer to have an exception instead of null, because (a) it fails loudly, so it's more likely to be fixed on development/testing phase, (b) it is easy to handle, because I can catch all rare/unexpected exceptions once for my whole application (in my front controller) and just log it (I usually send emails to dev team), so it can be fixed later. And I actually hate that standard PHP library mostly uses "return null" approach - it really just makes PHP code more error-prone, unless you check everything with isset(), which is just too much burden.
    – scriptin
    Jan 28, 2014 at 16:52
  • 1
    Also, if you return null on error, please please please make that clear in a docblock (e.g. @return boolean|null), so if I come across your code some day I wouldn't have to check function/method body.
    – scriptin
    Jan 28, 2014 at 17:02
2

Developing software is basically an art and a craft of managing complexity. You try to narrow down the system at the points you can afford to, and limit the options at other points. Function's interface is contract which helps to manage complexity of the code by limiting a knowledge required to work with any piece of code. By returning different types you significantly expand the function's interface by adding to it all the interfaces of different types you return AND adding unobvious rules as to which interface is returned.

2

I don't think this is a bad idea! In contrast to this most common opinion, and as already pointed out by Robert Harvey, Statically typed languages like Java introduced Generics exactly for situations like the one you are asking. Actually Java try to keep (wherever possible) typesafety at compile time, and sometimes Generics avoid code duplication, why? Because you can write the same method or the same class that handle/return different types. I'll make just a very brief example to show this idea:

Java 1.4

public static Boolean getBoolean(String property){
    return (Boolean) properties.getProperty(property);
}
public static Integer getInt(String property){
    return (Integer) properties.getProperty(property);
}

Java 1.5+

public static <T> getValue(String property, Class<T> clazz) throws WhateverCheckedException{
    return clazz.getConstructor(String.class).newInstance(properties.getProperty(property));
}
//the call will be
Boolean b = getValue("useProxy",Boolean.class);
Integer b = getValue("proxyPort",Integer.class);

In a dynamic typed language, since you have no typesafety at compile time, you are absolutely free to write the same code that works for many types. Since even in a statically typed language Generics were introduced to solve this problem, it's clearly a hint that writing a function that returns different types in a dynamic language is not a bad idea.

7
  • Another downvote without explaination! Thanks SE community!
    – thermz
    Jan 28, 2014 at 10:53
  • 2
    Have a sympathy +1. You should try opening your answer with a more clear and direct answer, and refrain from commenting on different answers. (I'd give you another +1, since I agree with you, but I only have one to give.)
    – DougM
    Jan 28, 2014 at 13:25
  • 3
    Generics don't exist in dynamically-typed languages (that I know of). There'd be no reason for them to. Most generics are a special case, where the "container" is more interesting than what's in it (which is why some languages, like Java, actually use type erasure). The generic type is actually a type of its own. Generic methods, without an actual generic type, as in your example here, are almost always just syntax sugar for an assign-and-cast semantic. Not a particularly compelling example of what the question is really asking about.
    – Aaronaught
    Jan 28, 2014 at 13:55
  • 1
    I try be a bit more syntetic: "Since even in a statically typed language Generics were introduced to solve this problem, it's clearly a hint that writing a function that returns different types in a dynamic language is not a bad idea." Better now? Check the answer of Robert Harvey or the comment of BRPocock and you'll realize that the example of Generics is related to this question
    – thermz
    Jan 28, 2014 at 15:17
  • 1
    Don't feel bad, @thermz. Downvotes often say more about the reader than the writer. Jan 28, 2014 at 18:16
1

Perl uses this a lot because what a function does depends on its context. For instance, a function could return an array if used in list context, or the length of the array if used in a place where a scalar value is expected. From the tutorial that is the first hit for "perl context", if you do:

my @now = localtime();

Then @now is an array variable (that's what the @ means), and will contain an array like (40, 51, 20, 9, 0, 109, 5, 8, 0).

If instead you call the function in a way where its result will have to be a scalar, with ($ variables are scalars):

my $now = localtime();

then it does something completely different: $now will be something like "Fri Jan 9 20:51:40 2009".

Another example I can think of is in implementing REST APIs, where the format of what is returned depends on what the client wants. E.g, HTML, or JSON, or XML. Although technically those are all streams of bytes, the idea is similar.

3
  • You might want to mention the specific way it does this in perl - wantarray... and maybe a link to some discussion if its good or bad - Use of wantarray Considered Harmful at PerlMonks
    – user40980
    Jan 28, 2014 at 16:01
  • By coincidence, I'm dealing with this with a Java Rest API I'm building. I made it possible for one resource to return either XML or JSON. The lowest common denominator type in that case is a String. Now people are asking for documentation on the return type. The java tools can generate it automatically if I return a class, but because I chose String I can't generate the documentation automatically. In hindsight/moral of the story, I wish I returned one type per method. Jan 28, 2014 at 18:00
  • Strictly, this amounts to a form of overloading. In other words, there are multiple federated functions, and depending on the particulars of the call, one or the other version is chosen.
    – Ian
    Jan 28, 2014 at 21:56
1

In dynamic-land it's all about duck-typing. The most responsible exposed/public-facing thing to do is to wrap potentially different types in a wrapper that gives them the same interface.

function ThingyWrapper(thingy){ //a function constructor (class-like thingy)

    //thingy is effectively private and persistent for ThingyWrapper instances

    if(typeof thingy === 'array'){
        this.alertItems = function(){
            thingy.forEach(function(el){ alert(el); });
        }
    }
    else {
        this.alertItems = function(){
            for(var x in thingy){ alert(thingy[x]); }
        }
    }
}

function gimmeThingy(){
    var
        coinToss = Math.round( Math.random() ),//gives me 0 or 1
        arrayThingy = [1,2,3],
        objectThingy = { item1:1, item2:2, item3:3 }
    ;

    //0 dynamically evaluates to false in JS
    return new ThingyWrapper( coinToss ? arrayThingy : objectThingy );
}

gimmeThingy().alertItems(); //should be same every time except order of numbers - maybe

It might occasionally make sense to poop out different types altogether with no generic wrapper but honestly in 7ish years of writing JS, it's not something I've found it was reasonable or convenient to do very often. Mostly that's something I'd do in the context of closed environments like an object's interior where things are getting put together. But it's not something I've done often enough that any examples jump to mind.

Mostly, I would advise that you stop thinking about types as much altogether. You deal with types when you need to in a dynamic language. No more. It's the whole point. Don't check the type of every single argument. That's something I would only be tempted to do in an environment where the same method could give inconsistent results in unobvious ways (so definitely don't do something like that). But it's not the type that matters, it's how whatever it is that you give me works.

Not the answer you're looking for? Browse other questions tagged or ask your own question.