Generic Programming (11)-Delayed Computation-lazyevaluation
A lazy evaluation delays the computation of an expression's value until the expression is actually used. Before discussing lazy-evaluation, it is useful to introduce one of the more unusual linguistic properties of generic programming, "computational timing" (strict-ness). strict-ness is the point-in-time mode in which the system computes the value of an expression: immediate (strict), or deferred (non-strict or lazy). Non-strict or lazy means that an expression is counted only when it is used. To illustrate with a simple visual example.
1 def lazyFun(x: Int): Int = { 2 println("inside function") 3 x + 1 4 } //> lazyFun: (x: Int)Int 5 lazyFun(3/0) //> java.lang.ArithmeticException: / by zero
clearly, When we put the 3/0 passed as a parameter to thelazyFun time, The system calculates the value of this parameter before entering the function, There was an anomaly in the calculation, The result did not enter the function executionprintln Just quit.。 Below we putlazyFun to change the parameter declaration of:x: => Int:
1 def lazyFun(x: => Int): Int = { 2 println("inside function") 3 x + 1 4 } //> lazyFun: (x: => Int)Int 5 lazyFun(3/0) //> inside function 6 //| java.lang.ArithmeticException: / by zero 7 //| at ch5.stream$$anonfun$main$1$$anonfun$1.apply$mcI$sp(ch5.stream.scala:1 8 //| 0)
In this example we are once again asking thelazyFun Pass in aException。 The system went inside the function this time, We seeprintln("inside function") It's still running.。 This means that the system does not heed the incoming parameters, Until the expressionx + 1 Use this parameterx Only calculated whenx values。 We see that the parameterx The type of the => Int, representativesx The parameters arenon-strict of。non-strict The parameter is recalculated once each time it is used。 Explained in terms of internal implementation mechanisms: This is because the compiler(compiler) run intonon-strict parameter will put a pointer to the call stack, Instead of the usual putting the value of the parameter into。 So every time you usenon-strict The parameters are recalculated when。 We can get confirmation from the following example:
1 def pair(x: => Int):(Int, Int) = (x, x) //> pair: (x: => Int)(Int, Int) 2 pair( {println("hello..."); 5} ) //> hello... 3 //| hello... 4 //| res1: (Int, Int) = (5,5)
In the above example we have provided thepair The function passes in a paragraph starting withInt kind 5 is the resulting code asx parameters。 After returning the results(5,5) Later from twohello... It can be confirmed that the incoming parameters are calculated twice。
In fact Boolean expressions in many languages(Boolean Expression) allnon-strict of, include &&, || 。 x && y Expressions in which ifx have a value offalse The system does not calculatey values, Rather, the result is straightforwardfalse。 equivalent x || y be like sth.x have a value oftrue When the system does not calculatey。 Just think ify How much computational resources can be saved if you need a few thousand lines of code to calculate。
Take another look at the following oneif-then-else examples:
1 def if2[A](cond: Boolean, valTrue: => A, valFalse: => A): A = { 2 if (cond) { println("run valTrue..."); valTrue } 3 else { println("run valFalse..."); valFalse } 4 } //> if2: [A](cond: Boolean, valTrue: => A, valFalse: => A)A 5 if2(true, 1, 0) //> run valTrue... 6 //| res2: Int = 1 7 if2(false, 1, 0) //> run valFalse... 8 //| res3: Int = 0 9
if-then-else functionif2 in the parameters of theif The condition isstrict of, but (not)then harmonyelse allnon-strict of。
You can see exactly how the arithmeticvalTrue orvalFalse All dependent on conditionscond The result of the operation of。 But in any case the system will only press the operation a。 Still the same., in casevalTrue harmonyvalFalse All large and complex calculations of several thousand lines of code, or sonon-strict Features will save a lot of computing resources, Improving the operational efficiency of the system。 beyond,non-strict Features an infinite data stream(Infinite Stream) the basic requirements of the, This section is in the next sectionStream The details will be presented in the。
Analyze it from the other side, though: the non-strict parameter has the potential to operate multiple times inside the function; if this parameter is used multiple times inside this function. By the same token, if this parameter is a large calculation, it again produces a waste of resources. The lazy declaration in Scala language solves the problem of multiple operations on non-strict parameters. The lazy val declaration not only defers the right-hand side of the assignment expression, but also has a caching effect: the right-hand side of the expression is only computed when it is actually made, and is not recomputed once the assignment has been made. Let's try to modify the above example a bit.
1 def pair(x: => Int):(Int, Int) = { //> pair: (x: => Int)(Int, Int) 2 lazy val y = x // no arithmetic, haven't started using y 3 (y,y) // the first y operation, the second one uses the cached value 4 }
In this version we use a lazy val)y. When this function is called, the value of the argument is computed once the first time y is used and then stored in the cache, so that when y is used later, the cached value is used without repeating the computation. You can see the result of the function call.
1 pair( { println("hello..."); 5} ) //> hello... 2 //| res1: (Int, Int) = (5,5)
Again, duplicate values are generated(5,5), But the parameter value operation is performed only once, Because there's only one linehello...