CS 330 Lecture 35 – Call by Name, Call by Need Cont’d
Agenda
- what ?s
- debug macro
- until loop in Scala
- lazy data structures
Note
Last time we looked at pass-by-name, which eschews the eager evaluation that we run into in many languages and instead delays evaluation to the point of reference. Sometimes pass-by-name is really useful. Suppose we wanted to write a debugging function that printed out the value of an expression, but also printed out the file name, the line number, and the expression itself. We might take this approach as a first attempt:
/usr/lib/ruby/2.7.0/rubygems/dependency.rb:311:in `to_specs': Could not find 'coderay' (>= 0) among 56 total gem(s) (Gem::MissingSpecError)
Checked in 'GEM_PATH=/.gem/ruby/2.7.0:/var/lib/gems/2.7.0:/usr/lib/ruby/gems/2.7.0:/usr/share/rubygems-integration/2.7.0:/usr/share/rubygems-integration/all:/usr/lib/x86_64-linux-gnu/rubygems-integration/2.7.0:/home/johnch/.gems', execute `gem env` for more information
from /usr/lib/ruby/2.7.0/rubygems/dependency.rb:323:in `to_spec'
from /usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_gem.rb:62:in `gem'
from ./coderay:24:in `'
But this approach has several problems. The __*__
variables point to the debug function, not the “debugger” function, making them not so useful. Additionally, we don’t have any way to print the expression, since eager evaluation has taken place long before debug
got the CPU. Instead, we can use a macro with its pass-by-name semantics. We can turn the expression parameter into a C string with the #
operator:
#define DEBUGI(expr) printf("[%s:%s:%d] " #expr " -> %d\n", __FILE__, __func__, __LINE__, expr)
int a = 20;
DEBUGI(a + 1);
Once again, we see that pass-by-name delays evaluation until an expression is needed, allowing us to do other things with the expression than evaluate it. Where else might we want to delay evaluation? If statements and loops. We want to pass the bodies to these structures but we don’t want to automatically execute them until we have more information. We’ll look at an example in Scala of an until
loop that demonstrates this.
We saw last time that we might want a pass-by-scheme that has the evaluation cost advantage of pass-by-value and the lazy advantage of pass-by-name. That’s called pass-by-need. We need to switch from eager evaluation to lazy evaluation, but once evaluated, we want to hang on to the expression’s value so that future references can reuse the previous result.
It turns out that lazy evaluation is how Haskell supports infinite data structures. Thinking how it models [1..]
will lead us to an implementation of lazy structures in Ruby.
Code
debug.c
/usr/lib/ruby/2.7.0/rubygems/dependency.rb:311:in `to_specs': Could not find 'coderay' (>= 0) among 56 total gem(s) (Gem::MissingSpecError)
Checked in 'GEM_PATH=/.gem/ruby/2.7.0:/var/lib/gems/2.7.0:/usr/lib/ruby/gems/2.7.0:/usr/share/rubygems-integration/2.7.0:/usr/share/rubygems-integration/all:/usr/lib/x86_64-linux-gnu/rubygems-integration/2.7.0:/home/johnch/.gems', execute `gem env` for more information
from /usr/lib/ruby/2.7.0/rubygems/dependency.rb:323:in `to_spec'
from /usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_gem.rb:62:in `gem'
from ./coderay:24:in `'
Until.scala
object Main {
// def until(condition: => Boolean, body: => Unit) {
def until(condition: => Boolean)(body: => Unit) {
while (!condition) {
body
}
}
def main(args: Array[String]) {
var i = 0
// while (i < 10) {
// println(i)
// i += 1
// }
until (i >= 10) {
println(i)
i += 1
}
}
}
lazy.rb
/usr/lib/ruby/2.7.0/rubygems/dependency.rb:311:in `to_specs': Could not find 'coderay' (>= 0) among 56 total gem(s) (Gem::MissingSpecError)
Checked in 'GEM_PATH=/.gem/ruby/2.7.0:/var/lib/gems/2.7.0:/usr/lib/ruby/gems/2.7.0:/usr/share/rubygems-integration/2.7.0:/usr/share/rubygems-integration/all:/usr/lib/x86_64-linux-gnu/rubygems-integration/2.7.0:/home/johnch/.gems', execute `gem env` for more information
from /usr/lib/ruby/2.7.0/rubygems/dependency.rb:323:in `to_spec'
from /usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_gem.rb:62:in `gem'
from ./coderay:24:in `'