Support lazy argument in functions


#1

From the documentation: “Support lazy properties: the value gets computed only upon first access.” It is not clear what “first access” means, but I think it should mean the first time the property value is used. Presently, it rather means the first time the property is used, which is different. A short example:

val lazyValue: String by lazy {
  println("computed!")
  "Hello!"
}

fun doNothing(s: String) = {}

fun main(args: Array<String>) {
  doNothing(lazyValue)
}

This should not evaluate the property, since the property value is not used. At least, there should be a mean to ask for lazy evaluation of the argument, such as:

 fun doNothing(lazy s: String) = {}

It is of course possible to emulate laziness using a function:

val lazyValue: () -> String = {
  println("computed!")
  "Hello!"
}

fun doNothing(s: () -> String) = {}

fun main(args: Array<String>) {
  doNothing(lazyValue)
}

but we lose the main benefit of having the right type for the lazy value (String, and not () -> String)


#2

And what would the signature of fun doNothing(s: String) be on the JVM?

The parameter can’t be string since that requires an evaluation of the lazy parameter. So that would require two function signatures for every kotlin function, e.g.:

public void doNothing(String s) // the normal call
public void doNothing(Lazy<String> s) // the lazy variant

And also require implementation variations of both. So I don’t think this is reasonably feasible.


#3

I would say that the function signature is the right type. That is what should be provided (which would require the calling function to pass a closure). Of course if you read the value multiple times it may make sense to wrap it up into a lazy for convenience.

fun f(stringGenerator: () -> String) {
  val lazyString by lazy(stringGenerator)
  System.out.println("The generator is called here: $lazyString but not here: $lazyString")
}

var i:Int = 1
f({ i.apply { i=i+1 } })

#5

I was thinking of what we have in Scala, where we can write:

def lazyValue(): String =  {
  println("computed!")
  "Hello!"
}

def doNothing(s: => String): Unit = {}

doNothing(lazyValue())
doNothing("Hello")

#6

I would have to say that I don’t think that implicit lambda creation (which is what scala effectively does) is all that great an idea. While verbosity isn’t great, it is helpful to know when you pass a function versus passing a value.


#7

It does not seem to be a problem for Scala programmers!


#8

I would extend your suggestion for vals in a body of a function. E.g. for case bellow:

fun someFun(arg1:String, arg2:Int) {
    when (arg1) {
        "a" -> {
            val someHeavyStr = "..." // Heavily computed string based on args
        }
        "b" -> {
            val someHeavyStr = "..." // Heavily computed string based on args
        }
        "c" -> {
            val someHeavyStr = "..." // Heavily computed string based on args
        }
        "d" -> {
            val someHeavyStr = "..." // Heavily computed string based on args
        }
        "e" -> {
            // But for this case we do not need that heavy string
        }
        "f" -> {
            // But for this case we do not need that heavy string
        }
        "g" -> {
            // But for this case we do not need that heavy string
        }
    }
}

To reuse the definition of the same heavy computation I can use a lambda:

fun someFun1(arg1:String, arg2:Int) {
    val someHeavyStr = {"..."} // Heavily computed string based on args

    when (arg1) {
        "a" -> {
            someHeavyStr()
        }
        "b" -> {
            someHeavyStr()
        }
        "c" -> {
            someHeavyStr()
        }
        "d" -> {
            someHeavyStr()
        }
        "e" -> {
        }
        "f" -> {
        }
        "g" -> {
        }
    }
}

But probably it would be nice to have a syntax shortcut for that like:

fun someFun2(arg1:String, arg2:Int) {
    lazy val someHeavyStr = "..." // Heavily computed string based on args

    when (arg1) {
        "a" -> {
            someHeavyStr
        }
        "b" -> {
            someHeavyStr
        }
        "c" -> {
            someHeavyStr
        }
        "d" -> {
            someHeavyStr
        }
        "e" -> {
        }
        "f" -> {
        }
        "g" -> {
        }
    }
}

#9

In Kotlin 1.1, you can define local delegated properties, which lets you write this as val someHeavyStr: String by lazy { "..." }


#10

Until then you can do val someString = lazy { "..." } which requires the value to be retrieved by someString.value.


#11

But is there something similar to the Scala code below?

def foo(papapa: => Int) = {  // papapa is a lazy evaluated param
  println a
}

It is more readable.


#12

Kotlin equivalent to the above Scala code is:

fun foo(a: () -> Int) {
    println(a())
}

Kotlin explicitly distinguishes between pass-by-name and pass-by-value/reference by design. It is explicit on a call-site, too: for { 42 }


#13

This is absolutely not equivalent. In Kotlin, a is of type () -> Int whether in Scala, it is of type Int (as far as the programmer is concerned), which is why you have to use a special syntax on both side in Kotlin. Scala makes this transparent. And knowing whether we are actually passing a value or a function is totally irrelevant. This is an implementation detail. What is important is how we can combine types. In Scala you can provide a lazy A when a A is expected. This is normal because a lazy A should be an A. In Kotlin, a “lazy” A is not an A. It is a perfectly respectable choice, but it should not be called “lazy”. It is a memoized (constant) function.


#14

Scala makes it transparant at the cost of being opaque on the call site by magically creating objects.

For me that is a very poor language design decision and one of the reasons I prefer Kotlin over Scala. I like my language being explicit at the right time. Furthermore if Scala does it one way it doesn’t mean it’s the correct/preferred/natural way, it’s not the benchmark language, it’s a language which got some things right and some things wrong.


#15

Scala makes it transparant at the cost of being opaque on the call site by magically creating objects.

Yes, that’s what all languages do. They magically create machine code that the processor can execute.

For me that is a very poor language design decision and one of the reasons I prefer Kotlin over Scala. I like my language being explicit at the right time.

I understand that this is what you prefer. This does not make it right unless you give meaningful arguments.

Furthermore if Scala does it one way it doesn’t mean it’s the correct/preferred/natural way, it’s not the benchmark language, it’s a language which got some things right and some things wrong.

I agree with this too. But who said that Scala was the benchmark language? If I thought Scala was the benchmark language, I would simply use it. I don’t care how Scala implements it. If the Scala implementation is bad, what not chose a better one rather than saying “it’s badly done in Scala so we should not do it”. Eventually, we have to do it right. Or to chose not to do it. But in such case, don’t call it “lazy” because it’s only a memoized constant function. A lazy value should be evaluated when the value is used. Not when it’s reference is used. And a “lazy” A should be an A, so it should be possible to use it everywhere and A is expected without any syntax change.


#16

[quote=“pys, post:15, topic:2127”]
Yes, that’s what all languages do. They magically create machine code that the processor can execute.
[/quote]To me there is a difference between creating anonymous objects out of thin air and translating the actual code. So the nuance is important here.

[quote=“pys, post:15, topic:2127”]
I understand that this is what you prefer. This does not make it right unless you give meaningful arguments.
[/quote]Certainly agree. Yet we are talking preference here, you simply prefer something different. [quote=“pys, post:15, topic:2127”]
A lazy value should be evaluated when the value is used. Not when it’s reference is used. And a “lazy” A should be an A, so it should be possible to use it everywhere and A is expected without any syntax change.
[/quote]Why should that be the case? It’s just your interpretation of “when it’s used”. I can easily argue that a value being passed as an argument is “being used”. An issue with late lazy evaluation might be that you might keep it’s parent object strongly referenced for way too long. See the following (naive) example:

class Foo {
  val a by lazy {bar()}
  fun bar() = 3
  
  fun baz(callback : (Int) -> Unit) {
    callback(a) // if a is lazy evaluated we're now leaking the whole object Foo
  }
}

#17

The problem here is not the creation of “machine” code, it is the fact that the function contract changes in a way that is not transparent to the caller. In regular function calls the code providing the parameter value is called exactly once. With lazy values it is called zero or one times. Passed lambdas may be called any number of times (documentation may be needed to tell the user). In code with side-effects it is important to know whether a code fragment is certainly called or not. The question would be, is the risk of errors larger than the cost of not having special syntax for callers.

Programming languages are to some extend based upon the preferences of its designers. Good languages carefully consider the impacts and reasons behind choices, and then follow their own architectural principles. Being stylistically coherent is perhaps the strongest feature of any language.

Delegate values are a very interesting aspect of Kotlin, but not something that you’d want to expose automagically (there are some implementation difficulties as well). Putting a delegate on an interface boundary without being explicit about it is asking for trouble.

What would be a worthwhile idea is the idea of parameter transformation, where a parameter is transformed on call. This is of course possible by having a private overload where the public function calls the private implementation after transforming the parameters, but having a direct way would be interesting. In that case delegate parameters would be valid as well. Something like

fun f(a: ()->Int by lazy(a)) {
  // a is a delegate provided by Lazy<Int> constructed with lambda a
}
fun g(b: ()->Int => b()) {
  // b is a regular Int that is initialised with the result of calling lambda b
}
fun f_impl(a: ()->Int) { // This is what it may look like in current Kotlin
  fun _f(a: Lazy<Int>) {
    // implementation using a.value (needed as delegate parameters are not supported
  }
  _f(lazy(a))
}

#18

Thank you about your replies… Now I know how to do the same thing as that f#cking Scala code do using Kotlin:

fun foo(func: () -> Int) {
	val value: Int by lazy { func.invoke() }
	println(value)
}

Cheers!


#19

It is not the most elegant way to do that thing in Kotlin. The most explicit (Kotlin) way of saying that your function accepts a lazy value is to say that it accepts a lazy value:

fun foo(v: Lazy<Int>) {
    println(v.value) // compute on first use, or may already got a computed value from caller
}

Then you’d use it like this:

fun main(args: Array<String>) {
    foo(lazy { 42 })
}

Unlike Scala, it is fully explicit, so there will be no surprises for people reading your code, nor it does not require any special syntax in the language. Note, that when you create a lazy value in Kotlin you can also customise its thread-safety mode with an additional parameter (it is synchronised by default).


#20

Thx!
This is exactly what I want! : )


#21

We need a “Kotlin best practice” in documentation: Kotlin is really well extensible!

Here another example: C# style events