typesschemelispkawa

What is the return type of (display 8) ? / What are void-valued expressions?


In the Kawa implementation of Scheme, the expression

(null? ())

obviously returns

#t .

But if I type

(null? (display 8))

into the interpreter, the output is

8#f

So it seems as if display is a function that does have a side effect, to wit printing the value, and some sort of non-null return value. Hmm. Maybe (display 8) returns 8? After all, both 8 and (display 8) show 8 in the interactive interpreter.

So I typed in

(= 8 (display 8))

and the response was

/dev/stdin:5:6: warning - void-valued expression where value is needed
java.lang.NullPointerException
    at gnu.math.IntNum.compare(IntNum.java:181)
    at atInteractiveLevel-5.run(stdin:5)
    at gnu.expr.ModuleExp.evalModule2(ModuleExp.java:293)
    at gnu.expr.ModuleExp.evalModule(ModuleExp.java:212)
    at kawa.Shell.run(Shell.java:283)
    at kawa.Shell.run(Shell.java:196)
    at kawa.Shell.run(Shell.java:183)
    at kawa.repl.processArgs(repl.java:714)
    at kawa.repl.main(repl.java:820)
8

So, (display 8) is not null but it is "void-valued"? What does that mean? Can I check for void-valued like I can null in Scheme?

Also, why did the 8 appear after the error message?


Solution

  • You are correct in your inference that display is a function that returns a value (in addition to having the side-effect of printing to the current output port). The value this particular invocation of display returns, however, is one that the read-eval-print loop simply chooses not to print when it occurs on its own as the result of evaluating an expression.

    Kawa has a number of special constants; one of these is #!void, which is equivalent to the result of evaluating the expression (values) (which means "no values at all"). If you get the value #!void from the read-eval-print loop, it won't print:

    #|kawa:1|# #!void
    #|kawa:2|# (values)
    #|kawa:3|# 
    

    This is because Kawa's read-eval-print loop uses display to print out the value that an expression evaluates to, and display will choose to print nothing when given #!void.


    In the specific case of your experiment comparing the behavior of 8 vs (display 8), there is actually a crucial difference in what is happening. when you feed any input to the interpreter, it:

    1. reads (and compiles) the input,
    2. evaluates the compiled expression to a value, and
    3. prints out the resulting value.

    So when you feed it 8, the printing is happening in step 3. When you feed it (display 8), the printing is happening in step 2, and then the printing from step 3 just prints nothing (because the value returned by (display 8) is one that the interpreter chooses not to print).

    One way to observe this distinction: build a list from the expressions of interest.

    #|kawa:1|# (list (display 7) 8 (display 9))
    /dev/stdin:1:7: warning - void-valued expression where value is needed
    /dev/stdin:1:21: warning - void-valued expression where value is needed
    7 9 (#!null 8 #!null)
    #|kawa:2|# 
    

    Here we see that during the evaluation step, the interpreter displayed 7 and then 9, and then built a list of three elements: #!null, 8, and then #!null again.


    The Kawa interpreter also warned us that our code seemed to have problems: the Kawa interpreter is smart enough during the read-and-compile step (which happens before the evaluation and printing steps) to analyze the code for potential problems. Here, it says "The result of invoking display is not meant to be used as if it were a normal value" (as compared to a number or a string).

    So this explains why you see an error message (because it considers the result of invoking display to be void-valued), and it knows that the treatment of such values may not match the user's expectations. (it also explains why the number 8 in your example is printed after the error message: Because the error message is generated during the "read" step, but the displaying happens during the "evaluation" step, as explained above.


    Why do I say "the treatment of such values may not match the user's expectations"? Well, from the experiment above where we ran (list (display 7) 8 (display 9)), you might infer that the result of evaluating (display 7) is #!null. But this is actually not true!

    In Kawa, #!null is a special constant, and it is distinct from #!void. For some reason, the Kawa interpreter is deciding that when you plug (display 7) into a list-constructing expression (or more generally, I think any context expecting a non-void value), that it can throw away the return value of (display 7) and plug #!null in there instead.

    Why do I say this? Well, there is another way to print out values in Scheme: You can use the write procedure. The display procedure is usually meant for "human (or end-user) readable" output, while the write procedure is meant to show more about the structure of the given datum, when available. For example:

    #|kawa:1|# (display "Hello World")
    Hello World
    #|kawa:2|# (write "Hello World")
    "Hello World"
    #|kawa:3|# 
    

    In the above, display threw away the information that I had a string, and just focused on the contents of that string, while write told me "we have a string here, it is 11 characters long. Here are its contents."

    SO, what happens if we write the result of invoking display?

    #|kawa:1|# (write (display 8))
    8#!void
    #|kawa:2|# 
    

    Here, we got no warning from the read-and-compile step. Instead, it preserved the value of (display 8). So it first evaluated (display 8) (printing 8 to the output), then it fed the value produced from that (#!void) into the write invocation.

    (I won't claim that this is the clearest semantics ever. But my inference is that Kawa's warn-void-used is telling us about cases where the compiler is allowed to plug in a #!null in place of a #!void)