https://kotlinlang.org logo
j

janvladimirmostert

07/05/2020, 12:45 PM
Proposal: when a lambda has a return type of
Any?
, treat a non-valid expression on its last line as Unit instead of complaining that non-valid-expression is not
Any?
See the example below:
Copy code
inline fun foo(action: String = "Unknown", f: (bar: String) -> Any?): String {
    f("TESTING")
    return "DONE"
}
This currently works
Copy code
fun doSomething() {
    var k : Int = 0;
    foo(action = "DoSomething") {
        // Empty lambda's return type seen as Unit
    }
}
This too works
Copy code
fun doSomething() {
    var k : Int = 0;
    foo(action = "DoSomething") {
        k++ // return type seen as Int
    }
}
This doesn't work
Copy code
fun doSomething() {
    var k : Int = 0;
    foo(action = "DoSomething") {
        k = k + 1      // <<--- "Expected a value of type Any?"
    }
}
Workaround
Copy code
fun doSomething() {
    var k : Int = 0;
    foo(action = "DoSomething") {
        k = k + 1
        
        "Garbage value to please compiler" // now it compiles fine, return type seen as String
    }
}
Also see the comment on YouTrack
Copy code
Concerning Unit, 6.6.3 of spec says -  "...This type may be used as a return type of any function that does not return any meaningful value."  I think lambdas and blocks whose last statement is an assignment qualify as not returning any meaningful value.
https://youtrack.jetbrains.com/issue/KT-20215
d

Derek Peirce

07/05/2020, 8:35 PM
All of the examples would compile if you change
f
to
(String) -> Unit
, is there a particular reason you need
Any?
instead?
☝🏻 1
k

Kroppeb

07/06/2020, 6:11 AM
Instead of the garbage string I would suggest using
Unit
j

janvladimirmostert

07/06/2020, 2:20 PM
You can't return something if the return type is Unit and having to put Unit on the last line is just slightly less worse than putting a String on the last line to make the compiler compile
Copy code
val results: List<Result> = transaction {
    it.selectTenRowsOfSomething()
}
Copy code
val results: Int = transaction {
    it.countSomething(...)
}
Copy code
var recordsChanged = 0
transaction {
    recordsInserted += it.insertSomething()
    recordsInserted += it.insertSomethingElse()
    // now forced to put a Unit hack here that looks totally inconsistent with the other examples
    Unit
}
There are some DSL use-cases as well
d

Dico

07/06/2020, 9:19 PM
You can infer the return type of the lambda, whatever it returns will be assign able to Any?
And the function itself should also be assign able to a function returning Any? because of covariance
fun <T> foo(block: () -> T): () -> Any? = block
j

janvladimirmostert

07/07/2020, 3:22 PM
that's actually not a terrible idea
Copy code
fun <T> foo(block: () -> T): T {
			return block()
		}

		var a = 0
		foo {
			a += 1
		}
		val result = foo<String> {
			"blah"
		}
both works doing it this way
that String type is inferred anyways
Copy code
val result = foo {
			"blah"
		}
so that works too
that being said val a: Any = 0 val b: Any = Unit val c: Any = (blah = 3) <<-- shouldn't this be treated as Unit ? or bar { blah = 3 <<-- shouldn't this be treated as Unit? }
When i use T instead of Any / Any?, it actually infers Unit
Copy code
fun <T> foo(action: String = "Unknown", f: (bar: String) -> T): T {
			return f("TESTING")
		}

		var a = 0
		val result1: Unit = foo {
			a += 1
		}
		val result2: String = foo {
			"blah"
		}
that seems inconsistent
Copy code
val result1: Any = foo {
   a += 1 <<-- doesn't compile
}
that
a += 1
returns Unit. As far as i'm aware, Any contains Unit, but not in the above example
Copy code
println(Unit is Any) ---> prints true
Summary: var a = 0 fun <T> foo(action: String = "Unknown", f: (bar: String) -> T): T { return f("TESTING") } fun bar(action: String = "Unknown", f: (bar: String) -> Any): Any { return f("TESTING") } // this works val result1: Unit = foo { a += 1 } // this works val result2: String = foo { "blah" } // this doesn't work (fair enough, can't assign Any to Unit) val result3: Unit = bar { a += 1 } // this doesn't work (fair enough, can't assign Any to String) val result4: String = bar { "blah" } // this doesn't work (implied return type is Unit which is somehow not part of Any) <<----- this case i consider a bug val result5 = bar { a += 1 } // this does work (implied return type is String which is part of Any) val result6 = bar { "blah" }
d

Dico

07/08/2020, 12:00 PM
Unit is obviously a special case. It often gets optimised to
void
in jvm compilations.
Any
is interpreted by the compiler is saying "I will return a value" whereas Unit is used in place of void
https://kotlinlang.slack.com/archives/C0B9K7EP2/p1594137389237900?thread_ts=1593953148.233700&amp;cid=C0B9K7EP2 The type parameter is likely inferred to be
Any
here, but you can explicitly override it to be
Unit
. There are good reasons for Any to be inferred, it allows you to easily widen a more concrete type so you can later assign other subtypes.
j

janvladimirmostert

07/08/2020, 12:04 PM
the inferred type is indeed Any, but can't assign Unit to Any even though
Unit is Any
outputs true
Explicitly setting it to Unit, then complains that Any can't be assigned to Unit
so when using T, the a += 1 infers to Unit, but when using Any, that a += 1 infers Any and not Unit