Differentiate functions by return type


#1

I have question about language design.
Why not to differentiate functions by return type?

What I mean:

    val node: Int = getNode(i)
    val node: Node = getNode(i) 

    fun getNode(id: Int): Int { ... }
    fun getNode(id: Int): Node { ... }

We can’t define this two functions because they have same signatures. So why no to differentiate it by return type? For example in case when two functions have same signature val should be declared explicitly (because type inheritance not works in this case).

Thanks.


#2

It would lead to ambiguities when you do not assign it to a value. For example:

fun aFunction(i: Int) { ... }
fun aFunction(n: Node) { ... }

aFunction(getNode(nodeId))

Which getNode(...) function should the compiler invoke here? It must choose 1. The compiler does not know the actual type of the node at this point.

In your case I would simple use different function names. This assumes you know that the node with the given ID is of the returned type:

fun getInt(id: Int): Int { ... }
fun getNode(id: Int): Node {...}

val intNode = getInt(i) // Inferred type: Int
val node = getNode(i) // Inferred type: Node

If you do not know the type of the node beforehand, you have to check the actual type of the returned value:

fun getNode(id: Int): Any { ... }

fun process(value: Int) { ... }
fun process(value: Node) { ... }

val node = getNode(nodeId)
when (node) {
    is Int -> process(node)
    is Node -> process(node)
    else -> ...
}

#3

But they would be the same kind of ambiguities that already occur with closures.


#4

I do not understand. Closures have a distinct type, and a variable/parameter can only hold 1 specific type of closure. Can you show code where closures result in an ambiguity?


#5

When inheritance comes into play it would also not work any more. Methods with same name and parameter list must have the same return type. This is the case in all OO languages I know of. I don’t know exactly the reason, but I guess it is because type matching algorithms would get to complicated, make the compiler slow or something like that.


#6

What kind of problem with inheritance? Could you please post some example like @jstuyts did. Because for now it doesn’t looks clear from your answer. Thanks!


#7

Well, it is about methods of same name and parameter list that are in the same inheritance tree (one overwriting the other). They must also have the same return type. As already said, there is no language I know of that would allow for it except dynamically typed languages and Objective-C (some weird hack to remain in the spirit of Smalltalk)


#8

Ok, looks like it same problem that I described in the topic

We can’t define this two functions because they have same signatures.

problem but not the reason :), except @jstuyts reason I do not see any other yet.

Actually what he mention can be resolved by compilation time error like

ERROR ambiguity in method parameter aFunction etc

and by adding something like as in this cases. That will cast if function have only one return type or will insert function with proper type.


#9

Your proposal does not add much (if any) value. As Kotlin is strongly typed, you already know which function you are going to invoke when writing the code. Having the same name for functions with different return types, adds the requirement to add a type to many (if not all) invocations of those functions.

What do you gain if you are able to write the following code:

fun getInt(id: Int): Int { ... }
fun getNode(id: Int): Node { ... }

val i1 = getInt(id1)
val i2 = getInt(id2)
val i3 = getInt(id3)
val n1 = getNode(id4)
val n2 = getNode(id5)
val n3 = getNode(id6)

As this:

fun getNode(id: Int): Int { ... }
fun getNode(id: Int): Node { ... }

val i1: Int = getNode(id1)
val i2: Int = getNode(id2)
val i3: Int = getNode(id3)
val n1: Node = getNode(id4)
val n2: Node = getNode(id5)
val n3: Node = getNode(id6)

#10
val map = mapOf<Int, Node>(getNode(), getNode())

Of course looking furthermore we can populate such maps just in some more fun way like:

mapOf<Int, Node>(obj::getNode) 

I just ask because I’m not very experienced programmer as Kotlins authors and do not have experience in compiler design. :smiley:


#11

Your syntax is incorrect and should be:

val map = mapOf<Int, Node>(getNode() to getNode())

I think the code above is unclear. I would expect Int to be a subtype of Node, or vice versa, because the same function is used for both the key and the value.

This is what it looks like with different function names. It is much clearer if you ask me:

val map = mapOf(getInt() to getNode())

Your second example does not make sense because obj:getNode will result in a function reference, and you want to map from Ints to Nodes.


#12

A closure expression can result in different values depending on the type of variable or parameter it’s being assigned to. So val x = { it.toString() } is invalid because it is ambiguous, while val x: (Int) -> String = { it.toString() }is valid. But the expression { it.toString() } can be many other types (Java functional interfaces, etc.).

Likewise, there’s nothing preventing a functional call expression to refer to different functions depending on the context (Rust has this, I believe).
That being said, I’m not defending it’s a desirable feature for Kotlin.


#13

Yeah, syntax was wrong, but anyway syntax not the case :slight_smile: I’m still do not see any good reason that prevents this feature. And as @ilogico I’m also do not say that it is very good feature for language and should be implemented.


#14

Implement a (toy) compiler, and you will understand. It complicates the decision for which function to invoke a lot.

And it complicates maintenance. Suppose you start with a single function:

fun getNode(id: Int): Node

val n = getNode(1)

Now add the second function:

fun getNode(id: Int): Node
fun getNode(id: Int): Int

val n = getNode(1) // This no longer compiles

Now imagine a compiler error popping up dozens or hundreds of times in a code base when you add a new function.