28
$\begingroup$

For a few years, I have been teaching a programming class to first-year electrical engineering students -- thus, this is their first contact with computer programming. The language used is C (although this year I started using Logo for the first few weeks as it presents a smaller barrier to entry for a gentler introduction to the practice of programming).

Although the students have issues with many topics of the course, many of them are with more complicated concepts such as recursions and data structures. However, one point that I feel should be easy, yet I'm somehow not getting across to them, is the importance of returning a value in a function.

Concretely, say I want to calculate the norm of a two-dimensional vector, and break it down into the smallest possible functions. So rather than writing something like this:

double norm(double x, double y)
{
  return sqrt(x*x + y*y);
}

I might write something like this:

double add(double x, double y)
{
  return x + y;
}

double square(double x)
{
  return x*x;
}

double norm(double x, double y)
{
  return sqrt(add(square(x),square(y)));
}

Although my students have no problem understanding this breakdown of functions, if I left it for them to write the code, they would probably write something along the following lines, which obviously wouldn't work, and they would be unable to figure out why:

double add(double x, double y)
{
  double sum = x + y;
  printf("the sum of x and y is %lf\n", sum);
}

double square(double x)
{
  double sq = x*x;
  printf("the square of x is %lf\n", sq);
}

double norm(double x, double y)
{
  double result = sqrt(add(square(x),square(y)));
  printf("the norm of (x,y) is %lf\n", result);
}

Note: this isn't actual code that any of my students have written. This is just how I imagine they'd write it if I just sticked to their suggestions during the coding session.

What kind of analogies, objections, examples, counterexamples or strategies could I try to get them to understand that, by printing from a function, the value is not "routed" to the rest of the program, and thus the remainder of the computation will have no access to it?

Extra details

I try to introduce the concept of a CS function by analogy with a mathematical function, say $f(x) = x^2$ (I understand there is the issue of side effects in a CS function, which don't exist in a mathematical function, but I do try to mention this point). I show them how a parameter is passed (in this case $x$) and an operation is performed on it. Passing a parameter is a concept that they grasp easily enough, however most of my students appear somehow unable to understand the seemingly symmetrical issue of returning a value from that function.

Usually, by the point I start talking about functions, I have already introduced the printf() function and shown how it can print the value of variables to the screen.

Then, whenever I propose a problem which uses a function and try to engage the class to help me write code for it, they always get stuck when it comes the point of returning the value from the function. They do understand the importance of getting the value out of the function, but they always suggest I do it using printf() rather than return. Clearly this introduces a problem for the program we are writing because although we can see the intermediate results, there is no (sensible) way to consume the output stream in the rest of the program.

Although they do understand the importance of getting the computed value out of the function, they seem to think it's OK if it's just printed with printf() rather than properly returned from the function. I try to object that once something is printed to the screen, although it does show you, the programmer, the value in question, the computer can't "read" a printed value back from the screen gives that it's now just an assorted bunch of pixels on the screen. I even tried to make some kind of analogy with trying to present a picture of a written word to Google (say "programming") and having Google search for that word. However, somehow they don't just get this point.

$\endgroup$
  • 1
    $\begingroup$ OK, I added a concrete example. $\endgroup$ – swineone Oct 4 '17 at 17:01
  • 16
    $\begingroup$ Why is this such a long question? Students cannot seem to differentiate between return and printf. Then, give an example. Please edit out all the noise. Meanwhile, let me share in the answer section what I did to solve this. $\endgroup$ – Jay Oct 4 '17 at 18:13
  • 6
    $\begingroup$ This misconception isn't special to students studying C. It is common in beginners who don't understand the operation of a machine separate from its operator. $\endgroup$ – Buffy Oct 4 '17 at 18:25
  • 5
    $\begingroup$ @Jay Sorry for the question being so long. I guess it is my style to try to be as thorough as possible, and also to point out the context of the issue and what I've tried. I've edited the question to add a heading "Extra details" and tried to keep the core of the question before that heading. $\endgroup$ – swineone Oct 4 '17 at 18:33
  • 4
    $\begingroup$ I remember that this was one of my big "aha" moments. My reasoning for why it was hard to grasp back then was that I was coming to it from BASIC, which had no concept of scope. Once I understood scope, the role of "return" became obvious. $\endgroup$ – pojo-guy Oct 5 '17 at 1:48

21 Answers 21

31
$\begingroup$

I think this is a problem where the answer is partly in the prevention. I've observed many classes where printing is the method used to access the results of calculations for several weeks, both within functions and within main(). Then, return values of functions are suddenly introduced, but, they only serve to pass the result to main to be printed once again. No wonder students think they are the same---they are functionally equivalent in just about every example!

  • Setting a standard that printing only happens in main() may help. Good coding practice too.
  • Talking about return values of functions early, and using sample tests (e.g. "What is returned by my_func(4)? By my_func(0)? By my_func(-4)?") may help.
  • Showing that it's possible to write code without printing, such as on CloudCoder or in Websheets, may help.

Good luck!!

$\endgroup$
  • $\begingroup$ Actually, for this year, I have decided to delay as much as possible introducing printf() to them. Sometimes I will provide code samples where it is used, but I did not formally introduce the concept to them. When someone does ask about it, I reply in jest that it is a very dangerous concept that made quite a few former students fail the discipline previously (which, in a way, isn't far from the truth). Unfortunately, having seen it in action a few times, they've already realized how it works and keep trying to use it. $\endgroup$ – swineone Oct 4 '17 at 22:42
  • $\begingroup$ @swineone Interesting. If you're using C, how can the students see their results without printing? $\endgroup$ – nova Oct 5 '17 at 1:46
  • 4
    $\begingroup$ As I mentioned somewhere else, I started with Logo, so many things can be done without printing (although, admittedly, drawing poses the same problems as printing). I'm introducing the subject of C to the class now and I'm trying to use the test-driven development methodology, where for now I will be supplying the tests and skeleton functions for them to write their implementation. Also, I plan on teaching them how to use the debugger as an alternative to printing. $\endgroup$ – swineone Oct 5 '17 at 3:39
  • 2
    $\begingroup$ @ctrl-alt-delor you seem to be inventing your own terminology here. If printf returns a value (which it always does) then it's a function. The fact that many people choose to ignore the return value is a different issue, and no different conceptually from writing int i, j, k; j = 1; k = 2; i = j, k;. $\endgroup$ – alephzero Oct 5 '17 at 22:40
  • 3
    $\begingroup$ @ctrl-alt-delor Maybe I'm being pedantic but those aren't just your rules, those are the rules of reality. As soon as you try to have a side-effect-free subroutine call a subroutine with a side effect the former is no longer a side-effect-free subroutine. No rules have been violated. Side-effect-free subroutines necessarily don't call subroutines with side effects. Its part of what makes them side-effect-free. $\endgroup$ – Matt Oct 7 '17 at 23:13
18
$\begingroup$

I think that this problem is pretty widespread, actually. I believe that it comes from a misunderstanding of the difference between what the program/computer can know/do and what the person operating it can know/do. I think the problem is worse for interactive systems where the printf sends its output to the screen in front of the person running the program.

Many students with this misconception will print the output of a function and then write code to ask the user to type it back in so that the computer can "learn" the earlier result and then continue.

It was more obvious that it didn't work in the (very) old days when print sent its output to a chain printer in the back room of the computer center and you had to go ask for it. **

It might be enough to just explain this to them explicitly, but maybe not.

So, you might have a little "active learning" exercise like this. You will want four "players", one for the operator, one for a function (main) that calls others, the third for a function (sum) and the fourth for another function (product) .

The operator wants to know the value of 3 * (4 + 5); It/he/she asks main for the result. Function main will actually print (pseudocode - omitting details)

main()
    x = sum(4, 5)
    y = product(3, x)
    print(y)
  • sum(a, b) wants to compute the sum of a and b and "do something" with it.
  • product(a, b) wants to compute the product of a and b and "do something" with it.
  • print writes a value on a piece of paper and hands it to the operator.

Now there are two versions of sum and product; like this (only sum is shown):

sum(a, b)
    return a + b

and

sum(a, b)
    print(a + b)
  • return x is implemented by writing a value x on a piece of paper and handing it back to the person who asked you to execute (this will be main in each case), whereas print x hands the paper back to the operator.

Now run the simulation with each version of sum and product. Note how with the printing version of sum, the paper goes back to the operator and the main (function/person) doesn't have any value to continue with so it gets stuck. With the return version, the sum goes back to main and so it/he/she can continue. Only one paper is ever handed to the operator and there is no getting stuck.

Write out the program fragments on index cards and give them to the players. The "sum" player has only one version of the function in each simulation. The "main" player has the code for main above.


Note that print and return always operate the same - paper to operator vs paper to caller. The operator actually asks main for an operation: "Main run".
Player "main" actually asks sum and product for an operation: "sum 3, 4"

I've found it helpful to have sticky notes with the role names that the players can stick on their shirts.

It is probably necessary to do the return version first. This will make the contrast more clear. You may have to run each more than once, and you may have to talk through the "execution". You should almost certainly ask for questions.


Let me note two things about this idea:

  1. Lots of misconceptions can be dispelled with a "play acting" active learning exercise like this. "Play the computer" is often helpful.

  2. Doing this proactively, as part of introducing return or print is probably not a good idea. Most students will "get it" from a simpler and shorter explanation. So this is the sort of thing you have in your kit bag for emergencies.

$\endgroup$
  • 8
    $\begingroup$ The play-acting idea sounds fantastic. I wonder if it might help to "implement" return by whispering rather than passing a paper? Or alternatively, make print() write the result on a chalkboard visible to the class (not the actors)? The idea being to show print() and return as being very different actions. $\endgroup$ – David Z Oct 5 '17 at 1:04
  • $\begingroup$ Many variations are possible. Just think it through before you implement it. And expect surprises. Things can go wrong, so be prepared for a backup explanation. $\endgroup$ – Buffy Oct 5 '17 at 10:41
  • $\begingroup$ make them chaining the calls, that's exactly what I though when I red the post. RIP printf solution. $\endgroup$ – Walfrat Oct 5 '17 at 14:51
12
$\begingroup$

This is such a consistent trap that I ultimately created a worksheet to deal with it. At this point in my course, I have recently covered binary and hexadecimal, so I also use this worksheet as an opportunity to gently review those concepts.

My worksheet may not be as physically active as some of the lessons that have already been posted, but it is highly practical. The entire activity takes 10 minutes at most, it reviews some additional concepts, and (because the distinction being made is fairly simple) they never seem to get confused again afterwards. It could also be given as a very easy homework assignment.

The top reads:

Sometimes we use System.out.println, and sometimes we use return. Remember the difference? Returns are for giving information back to the computer, while printlns are for giving information in text form directly to the human user.

Consider the following scenarios. For each scenario, if it would be appropriate to use a return, write 1. If it would be appropriate to use a println, write 0. At the end, please write out the secret hexadecimal message!

What follows are 16 short questions in a table. And just to give you a flavor for the kinds of questions asked, the first question is:

In a 3D rendering program at an animation studio, a method calculates a double representing the exact value of brightness of each pixel in the frame.

The final message is the hexadecimal number f1ea, which is nice because fleas are so cuddly it is easy to check. (As an added bonus, kids who answer incorrectly are forced to decode the hex back to binary to figure out which questions they need to reconsider.)

Reviewing the worksheet in class takes almost no time at all. The kids get a kick out of the secret message, it reviews a prior concept, and (as I mentioned above), the entire activity is quite short.

$\endgroup$
8
$\begingroup$

Whenever I run into students who are baffled by the return concept (and in your case, them substituting it with printf), I do something like this.

I use role-play. I become one function (lets say the main function) and the student is another function (lets say some function add that adds two numbers and returns the values).

Here is how the role play works.

  1. I write two numbers on a piece of paper and give it to the student. In the code, this is like, I am the main function calling the add function and sending two values.
  2. Now, the agreement is that the student cannot just shout the answer (this is like printf in the code)
  3. Since shouting is not an option, the only way the student can let me have the answer is to 'return' me a piece of paper which contains the answer, which is the total of the two numbers I gave in step 1.
  4. So, the student returns me the piece of paper. This in the code becomes, the value being returned.

What I do is, I emphasize the fact that printf is like shouting, which does not help me as I need the answer on a piece of paper that must be returned explicitly.

$\endgroup$
  • $\begingroup$ I can see that making it physical does help with the idea of 'message passing' and even mailboxes, but this seems very close to the described 'didn't work' option. $\endgroup$ – Sean Houlihane Oct 4 '17 at 18:49
  • 1
    $\begingroup$ Worked for me for 5 years and over 5000 students. So, yeah, it works :) $\endgroup$ – Jay Oct 4 '17 at 18:55
  • $\begingroup$ I like it, You could ware ear defenders, to show that you can not hear. But what is “comparison printf”? $\endgroup$ – ctrl-alt-delor Oct 5 '17 at 7:56
  • $\begingroup$ oh man, I messed that sentence up. fixed it :P $\endgroup$ – Jay Oct 5 '17 at 11:35
  • 1
    $\begingroup$ It just so happens I do a lot of what you said. I pretend I am a computer and then, do literally nothing until I am explicitly told. I can be pretty dramatic at that point to drive home the point that computers are fast, efficient but totally just dumb. $\endgroup$ – Jay Oct 5 '17 at 12:23
7
$\begingroup$

Have you considered not teaching them about printf at all?

99% of the use of printf is as a poor-man's debugger. Teach them how to properly use a debugger to step through their code, and inspect their variables, rather than defensively printing out everything all the time, and doing post-mortems on log output.

$\endgroup$
  • $\begingroup$ Real programmers don't use debuggers (or printf). OK I do sometimes. However when I use design by contract, and/or test first. I don't need them. I have not done these in a teaching environment (yet), but they are very powerful, and result in code that work, and stays working. No need to debug. $\endgroup$ – ctrl-alt-delor Oct 5 '17 at 8:06
  • 5
    $\begingroup$ I am firm proponent of caveman debugging. printf should be okay. $\endgroup$ – Jay Oct 5 '17 at 11:37
  • $\begingroup$ Debuggers are a very bright almost laser-like spot light. You can view one aspect of the code (in space and time) very vividly. But you don't get a full picture. You have to shine that light in a lot of places to understand the full picture - this takes time. On the other hand, print statements are like flash lights. You can get some understanding of the entire picture but not all the details. Most of the time, flashlights work well. $\endgroup$ – Harrichael Oct 7 '17 at 16:13
  • $\begingroup$ @Harrichael Breakpoints can execute arbitrary debugger instructions, like printing variables, without polluting your release application $\endgroup$ – Alexander Oct 7 '17 at 16:21
  • $\begingroup$ I'll keep that in mind, but I would definitely recommend removing print statements after debugging - I would shudder at leaving them in. $\endgroup$ – Harrichael Oct 7 '17 at 16:28
5
$\begingroup$

The first metaphor that comes to mind is language; functions can act as verbs or nouns. So, for example, we can have:

double print_sum(double x, double y)
{
 double sum = x + y;
 printf("the sum of x and y is %lf\n", sum);
}

We can then have a line that says “print_sum(1,2)”, and the computer will print the sum of 1 and 2. Here, “print_sum” is a verb, and the parameters are the objects of that verb (the implicit subject is the computer, of course). Thus, saying “print_sum(1,2)” is a complete sentence. “print_sum” is an action, and putting it in your code tells your program to perform that action.

On the other hand, we can have:

double sum_of(double x, double y)
{
 return x + y;
}

Here, “sum_of” is a noun. “sum_of(1,2)” is not an action, it’s a thing. Having a line that consists of nothing but “sum_of(1,2)” wouldn’t do anything; it’s not a complete sentence. However, it can be the object of a verb; for instance, “printf(sum_of(1,2))” would be a complete sentence.

Advanced programmers naturally go back and forth between treating functions as verbs or nouns, and sometimes use them as both (that is, they use the return value in a calculation while also taking advantage of their side effects), although many people frown on such behavior. Your students clearly aren’t able to distinguish these two attributes on their own, so you need to make it more explicit. Naming a noun-function “add” really isn’t helping; that’s a verb-name, and so your students are going to be expecting it to be used for its effects, rather than its return value. If you were explaining in English how to calculate the norm, would you say “Take the square root of add the square … “? Have your students pay conscious attention to whether a function is being used as a noun or a verb, have them give them appropriate names, and model this by giving your functions appropriate names.

If a function is being used as a verb, then you can just run the function, and whatever code is in the function will be run. If you’re using the function as a noun, however, then you need to tell the program what value to assign to the function. That’s where the “return” command comes in. The “printf” command is still in verb-land. It’s doing something, not being something; if you write printf in sum_of, you’re telling the program what sum_of does, not what it is. When you write “return x+y”, on the other hand, you’re telling the program that “x+y” is what sum_of is.


Another metaphor is an office worker with an in-box and an out-box. The worker corresponds to a function, the in-box corresponds to the parameters, and the out-box corresponds to the return value. We can also imagine something analogous to printf, such as the company twitter account. When students put printf in a function, and then try assigning something to the function, the analogous situation is a worker tweeting the result of instead of putting it in their out-box.

$\endgroup$
  • $\begingroup$ Brilliant explanation of command query separation, and of naming methods. The only thing that I would add is adjectives, for when a method returns a boolean. Then more advanced adverbs or adjectives for enums (you should never pass a bool into a method, it will be, humanly, unreadable). (I have used the word method here, to mean subroutine, as I use function as a query, the noun/adjective thing, and procedure as a command a verb thing.) $\endgroup$ – ctrl-alt-delor Oct 5 '17 at 17:48
  • 1
    $\begingroup$ +1 function does what the name says it does!! sum() doesn't say print so it shouldn't print! $\endgroup$ – RonJohn Oct 5 '17 at 21:41
  • $\begingroup$ @RonJohn that might not work because: it also doesn't say return so it shouldn't return! $\endgroup$ – user253751 Oct 10 '17 at 3:55
  • $\begingroup$ @immibis that's so inane, I lost 3 IQ points just reading it. Do you know the slightest thing about math, and what functions even are? $\endgroup$ – RonJohn Oct 10 '17 at 4:21
  • $\begingroup$ @RonJohn Do your students, who you are trying to teach the difference between print and return to, and who probably do not understand mathematical functions any better than they do programming functions, know the difference between print and return? $\endgroup$ – user253751 Oct 10 '17 at 4:38
4
$\begingroup$

The conceptual issues here are that the examples in the OP are completely artificial (intelligent students should be asking "why on earth would you want to write a function to add two numbers, in C, when the language already contains a + operator") and that they only call the function once.

You could solve the first issues by a digression into operator overloading (which a student intelligent enough to ask the question should be able to understand!).

For the second issue, invent a situation where you call your function many times, but only want to print out one "answer". It should then be clear to the students why you don't want 1,000 lines of useless print coming from inside 1,000 calls to your "add" function!

$\endgroup$
  • 1
    $\begingroup$ I imagine you could also solve the first issue by using a more complex function... such as the absolute value (provided they don't yet know it's built in) or vector length. $\endgroup$ – user253751 Oct 10 '17 at 4:01
3
$\begingroup$

However, one point that I feel should be easy, yet I'm somehow not getting across to them, is the importance of returning a value in a function.

Then focus on that.
I know, you think you are doing that (so much, that you've gone to the extent of asking here about that). However, you're not, as I will demonstrate.

People love to have their needs fulfilled. So, give them some clear needs.

Teachable moments:

First, I'd like to point out some possibly-great teaching moments that this code offers, so you don't miss this opportunity:

if I left it for them to write the code, they would probably write something along the following lines, which obviously wouldn't work, and they would be unable to figure out why:

(3 functions that each "printf")

Looking at those examples, the "square" function takes a value of x, and the printf you wrote states, printf("the square of x is... Later, you call square(x),square(y). I'm guessing that some (and perhaps many) students would be perplexed by the result. When you call "square(x)", it says "the square of x is". However, when you call "square(y)", the code says "the square of x is". Some students might not understand the discrepancy (of why the code says x when it refers to a value which is clearly identified as y), which they may not have noticed until you point it out (because when they read the source code of the square function, everything looks like an x and so it makes sense. But then you point out something that doesn't make as much sense, and they may wonder why it doesn't make sense.)

This conundrum could be addressed by focusing on the situation in 3 different ways:

  • the passed value ("when we say x, we really mean the first parameter"),
  • or the difference between "x" as a variable and "x" as part of a literal string,
  • or looking at the "scope" of a variable, pointing out that we have multiple x's, and so the y in the norm function becomes the x of the square function, and that doesn't affect the x in the norm function, because references to x are not affecting a global variable named x

So there are multiple teachable points to bring up.

Simplification Helps:

Now, another item I'd like to note about this code...

You're also complicating things by taking add()'s return value and passing it to sqrt(). So you have a double-call, which may be harder to grasp than a simpler example. You could simplify the example simply by storing add()'s results to a variable that will then get passed to sqrt(). For experienced experts like us, that looks like an extra variable and needless complexity that makes the code longer. However, breaking it into separate steps, where each step is short and direct, is actually simpler and will be easier for people to grasp.

Part of the reason I'm pointing this out now is so I don't need to explain it in the middle of my upcoming finale (to reduce how much I jump to one topic in the middle of explaining another topic).

People love to have needs solved:

Now, I'd like to get back a bit more to focusing on the solution to your base question.

I point out that your students may be quite "right"... from their perspective. And that may be why they are struggling to grasp what you're trying to demonstrate/teach to them. Their paradigm may need to be shifted.

What is the goal? If they think the goal is "to get the computer to print out the needed data", then it is very sensible for them to want to "print out data as soon as we have it, and then have no more use for the data" and allow the data to be discarded without being returned. That actually seems more efficient than trying to retain the data just to do work later. They may be following some principles of efficiency, which might actually be a good thing.

I suggest that the solution might be to provide another example. Demonstrate the use of a returned value. Note: I did not just say, "demonstrate a return value being used". You technically do that, as the norm() function's add() call uses what gets returned by "square". (And you actually do that twice, as I noted earlier when I mentioned you're doing a complicated nested/double call.) What I'm saying is to clearly show just how the concept ends up being useful.

Take the returned value, and demonstrate using the returned value multiple times. First takes the results of square(x), and pass that to add(). However, then do something else useful with x. (Print it out again, in another context. Maybe perform a test of some sort, and ensure that is done after the value would be corrupted by an earlier step.)

Then they will see why the main() function needs to keep track of x. The simple reason is that the value of x gets used multiple times, over and over again. They can't just "print and forget" x, because the value of x because a valuable thing, and therefore must be preserved and protected.

If you want the students to understand the concept of a function returning a value, then give them a reason to see a need for the concept being effectively used. As described in the prior two paragraphs, that can be very simple in nature. When you can show them a simple-to-see example of how it is being used effectively, their natural instinct will be, "Well of course that's how it works. That would be the simple and straightforward method of accomplishing exactly what I see is being accomplished." Once they see those mechanics being clearly used, they will more readily understand how the mechanic operates. Then they may be more open to understanding just how that can be effectively useful in different scenarios (which hopefully you can demonstrate with some more prepared examples).

Syntax Error:

Note: the code you showed is buggy. Well, we knew that. The desired point of the square function is to make sure that x is usefully being returned, and the problem you point out is that the example code just tries to use x immediately without returning it. A possibly-great thing to do is to show the problem, by demonstrating what happens when x isn't returned, and then x not working as designed in a later location when x is needed. However, you may not be able to effectively demonstrate that bug, because there is another bug. The code just simply won't run at all, because the C complier is going to complain that the "square" function is declared to return a double, but then there isn't a return statement. Since you have a syntax error, you won't be able to get cooperation from the C compiler, which means you won't be able to run wrong code that demonstrates the logical flaw in the code.

You may want to correct the code so that it looks better, and instead of the compiler visibly reporting a problem, you end up having runnable code that is flawed. (Instead of an obvious error, you have an error that is more difficult to notice. That's actually worse. Demonstrating that can be another great thing that you can teach them.)

Review/Summary:

  • The key answer to your question is: demonstrate the worth of having x being returned, so that x can be used multiple times by the calling function
    • That's the simple core of the answer that directly addresses your main question
  • I see several other opportunities of bringing up some topics that would be good to point out to novices, and so I've pointed them out so that you can take advantage of these instead of missing any of the ones I noticed.
    • The different ways that the symbol "x" gets used provides multiple teachable moments.
      • To keep this summary shorter, I don't repeat every one of those topics, but just look for the prior bullet points, because each one of those bullet points represents an important topic that is worth independent discussion.
    • Try not to do any more than one function call at a time until they understand the mechanics of the life cycle of calling a function, including the return value
    • You've positively set yourself up with an excellent opportunity to demonstrate how a very-wrong error can cause visible complaints by the compiler, but a sneaky error may provide okay-looking wrong values, which can be harder to notice and are, therefore, even worse.

$\endgroup$
  • $\begingroup$ Long, but very useful. $\endgroup$ – Sean Houlihane Oct 5 '17 at 11:58
3
$\begingroup$

I have found the key is to illustrate the difference between side-effects and returns.

When teaching functions, I do an exercise called "guess the function" where I tell students to write a number on a post-it and hand it to me. I then add 3 to the number, write it on a new post-it, and hand it back to the student. I repeat this a few times with squaring the number and other slightly more complex operations, always handing back to the student a post-it with the result.

Next, I'll chain together two functions using a student volunteer who I instruct to add one to the number. I then square it and hand it to the original student.

After several rounds, when I feel my students have become comfortable with the pattern, I change my behavior. When a student hands me a post-it, I write "Your number was " on the board, substituting their number. I also take the original post-it and ceremoniously throw it in the trash. I repeat this with different operations (division, etc). I ask a student to explain the difference to me.

Students are now primed for an explanation of the difference between returns and side-effects. They see how I can't chain together two actions when I write a number on the board, but I can when I write the number on a post-it. It's a worthwhile 10 to 15 minute exercise.

$\endgroup$
3
$\begingroup$

After doing @Buffy's active learning, the next step would be.

For you to to create the program, and remove the guts of the functions. Just the skeletons for the students to fill in.

int main(){
  print norm(4,7);
}

double add(double x, double y)
{
  return 0;
}

double square(double x)
{
  return 0;
}

double norm(double x, double y)
{
  return 0;
}

They have to take your code and change the 0s to the correct code.

Parsons problems

Give them all of the correct code, but in the wrong order. They have to rearrange it.

Pupils can do a lot of these in a short time. Each one is, supposedly, nearly as powerful as doing a normal programming exercise. However you can do more of them, and there is less frustration.

$\endgroup$
2
$\begingroup$

My guess is that the gap in the learning comes from not recognising what variables are and the concepts we're familiar with for data storage. I can imagine that for maths/engineering students, the fluidity and connected nature of equations makes the problem worse.

In circuit design for example, modules will be familiar. These are blocks that have interfaces, some inputs, some outputs, some power. The operation of the module is a continuous process, connections being made with wires.

There are two connected parts that I think will be useful to show to your students about what a computer program is trying to represent (and the fact that it's an abstraction might be something to explain to them).

Firstly, computers perform basic steps one at a time (or in blocks, or several at a time, but always in a limited scope). Think of buckets of water rather than a hose pipe.

The second is the concept of doing the work on individual blocks of data. This data has to be stored somewhere. It could be the water in your buckets (which can be split up, coloured, mixed, etc). Computers don't do mixer taps, they have to take the items they're working on piece by piece and work on each element. If they know about integration using calculus, the difference between discrete and continuous items shouldn't be a problem.

I think if you review the way you're teaching variables, also show that to process code, a computer (a general purpose machine) follows its instructions to operate in steps, it will be easier to show that the result of an operation always needs to be stored somewhere (not just for functions), so a program is different to a circuit. An electrical module is fixed at it's location in a circuit, but a software function can be called from different places.

In a program, whenever a function is called, there is a place defined to put the result. This isn't 'in' the function, it's 'in' the code that calls the function. (of course, if it's not used, then the computer knows it doesn't actually need to bother with it).

I'm not sure you need to have your students moving the data, but showing containers for data does help. The numbers in envelopes fits best if you use it to show how add, multiply etc. work - before you get to the functions. Playing out the operation of the machine requires a few more steps to make it complete, if you act it out, then you can avoid some of the details that they don't ask about...

$\endgroup$
2
$\begingroup$

I want to give a really simple solution.

Let 2 students stand up, student 1 = s1 = caller, student 2 = s2 = function

Tell one to take a pen with a cap and give it to s1

s2 is the function.Tell s1 to give the pen to s2 ("parameter")

s2 has to remove the cap ("function")

Now you have 2 options to tell the s2, aka the "function" what to do after executing, return or printf

if s2 hands the pen with removed cap to the s1, he "returned". If s2 tells s1 what he did it's "printf"

$\endgroup$
  • $\begingroup$ another role play thing. Role play is useful when teaching methods. $\endgroup$ – Jay Oct 5 '17 at 1:56
2
$\begingroup$

Try to keep it simple.

printf() is a function for humans. Anything which printf() outputs is ignored by the program.

return() is a function (a control instruction) for the program. Anything it does is not (directly) observable by the human.

Of course, everyone here knows about side-effects and that printf() actually returns something, but I guess we don't shove the sad students into such areas...yet. However, it would be interesting to check what happens when you start explaining scanf() by starting with buffer overflow exploits and machine code...

$\endgroup$
  • $\begingroup$ scanf...that will drive those who cannot even get printf mad. $\endgroup$ – Jay Oct 5 '17 at 11:38
2
$\begingroup$

Based on your question, I infer that you think your students are "not getting it" and need further explanation.

However, when I look at the students' response, I suspect that they are getting it, but you simply have not presented them with a scenario where the two are not functionally equivalent.

Present them with a problem that cannot be solved without returning values.*

For the square() example, you could use the Pythagorean theorem (how to calculate C from A and B), or you could e.g. ask them to calculate the square of a square using only a method that squares a number (and does not immediately calculate the subsequent square).


*If you claim that there are cases where printing values is simply not the same as returning them, then the onus is on you to show an example to back up your claim.

I like the way your students approach the things you're trying to teach them. Instead of blindly accepting what you say, they are testing your claims by trying to do it their way. Prove them wrong.
This is annoying for you now, but that sort of behavior will do wonders for them if they ever start coding independently.


double square(double x)
{
  double sq = x*x;
  printf("the square of x is %lf\n", sq);
}

If your students think that this is okay (I'm aware it's not their own code, but your example), then they are thinking in terms of a single method call being able to solve their problem.

In an application that takes a number, squares it, and returns the result, it's indeed functionally equivalent (though obviously bad practice, but they don't see that yet).

However, how would they write the method if the application is supposed to apply the Pythagorean theorem? (i.e. calculating C based on A and B) This requires multiple squares to be calculated.

(Obviously, you have to enforce the use of a square() method, or otherwise they would write the formula in a single line, defeating the purpose of having to take multiple steps)

Without returning values, it would become impossible to calculate the value of square(A) + square(B), since the only thing the application ever does is display the square of a number. Without a return value, the application will be incapable of actually using the number for further calculations.

$\endgroup$
  • $\begingroup$ Review: The 1st section is just more of the same (see other answers). I nearly got bored and stopped reading. However the 2nd section onward has value. $\endgroup$ – ctrl-alt-delor Oct 5 '17 at 14:50
  • 1
    $\begingroup$ @ctrl-alt-delor: Swapped the sections around to improve the answer :) $\endgroup$ – Flater Oct 5 '17 at 14:53
2
$\begingroup$

Same problem here for some time as students were taught in earlier classes to write algorithms as input → compute → print.

I teach in Python so using the console I can refrain completely from using print, but found that I had to explicitly disrupt the bad pattern.

Students do not need to fully understand the notion of value returning in the first course (the third one is sufficient), what they need to understand is that printing is more restrictive than returning. This is easy to explain that one can always decide to print a value one got from a return but unprinting is not possible.

So when writing code that you want to be able to reuse, you return.

If they can manage a little arithmetic, the following exercise is nice :

  1. Write a naive function that tells if an integer is prime.
  2. Write a function that tells if an integer n is pseudo-prime (defined as : for all a < n, n divides a^n - a).
  3. Find the smallest integer that is pseudo-prime but not prime (561).

This will be unsufferable if the first two functions print instead of returning.

$\endgroup$
  • $\begingroup$ Is there a logic error in paragraph 2? (a negation/ non-negation) $\endgroup$ – ctrl-alt-delor Oct 5 '17 at 8:09
  • $\begingroup$ I do not think so. I am defining n(>=2) to be pseudo-prime iff for all 2<=a<n, n divides (a^n-a). Is this weird ? Other notions of pseudo-primality exist but this one is nice for the example. $\endgroup$ – ysalmon Oct 5 '17 at 19:51
  • $\begingroup$ a paragraph is not a bullet point. “I teach in Python, so using …” $\endgroup$ – ctrl-alt-delor Oct 6 '17 at 7:40
1
$\begingroup$

Many helpful suggestions all around, from each of the different answers. In reading all of them, I was inspired to follow along with the advice of role-playing in a specific way that I thought up, which I will try to describe in this answer:

First, I need to choose a piece of example code, say the vector norm one in the question or a different one, but suppose it's the vector norm for the following exposition.

I will get students to volunteer for acting out the different functions: main(), square(), add(), sqrt(), norm(), printf() and (although this is somewhat conceptually wrong, but I believe will be helpful) return. Code will be provided in two styles for each of the computational functions (square(), add(), sqrt() and norm()): the correct way using return and the wrong way using printf().

Passing of parameters will happen by writing values to a piece of paper. Students should write the required values and keep them hidden. Whenever a function call takes place, the student acting out that function will only go ahead with the computation once he gets out the required pieces of paper corresponding to the parameters passed to it.

The key point I am considering to make is to distinguish between the insides of the computer (corresponding e.g. to the CPU) and what's visible by the outside user (e.g. the screen). Thus, I will erect a physical barrier (possibly using e.g. a wall of students) between the inside and the outside of the computer.

When acting printf() out, I will request the corresponding student to breach the barrier between the inside of the computer and the outside, stepping to the other side of the wall of students, and then showing the piece of paper for everyone to see (as if the rest of the class represented the user of the program). However, the students on the inside of the barrier (representing functions executing on the CPU) will be unable to see the piece of paper shown by the printf()-acting student. Also, I will request that the printf()-acting student hide each piece of paper after showing them to the rest of the class. Hopefully this will make it clear to them that after printing something with printf(), you're starving the functions that come after it of the required data to continue the computation.

On the other hand, the student acting out return will be asked to deliver the piece of paper to the student acting out the calling function, but not show it to the rest of the class (i.e. not breaching the barrier between the inside and outside of the computer).

Hopefully, after acting out the two versions of the code (using printf() and using return) in this way, it will become clear to the students exactly what happens in each situation. After trying this out and giving some time to see if the students indeed grasped the importance of return vs. printf(), I will report back here on whether the idea worked. Also, I appreciate any comments on ways to improve this specific strategy that I have described.

$\endgroup$
  • 1
    $\begingroup$ I think it's overkill. I think it would be faster and more effective to thoroughly clear the words involved. For example, actually thoroughly clearing the words "return" and "function" and "operator," rather than taking them for granted as understood. To the extent that your approach works, I think it will be because it conveys the meanings of these words—but I think that by explicitly addressing the definitions, it could be done faster and more directly. $\endgroup$ – Wildcard Oct 5 '17 at 0:13
  • $\begingroup$ @Wildcard I prefer 'define' or 'disambiguate', not another made up use of an existing word (with all the baggage that comes with cliques). $\endgroup$ – Sean Houlihane Oct 5 '17 at 11:55
  • $\begingroup$ @SeanHoulihane I can agree with that, but I do not simply mean "define" or "disambiguate." Those words do not convey the depth of understanding I refer to (nor the precision of steps involved in attaining it). Clearing a word is a precise action; much as the phrase "worst-case time complexity" should not be equated to "how long it takes to run." I can attest to the effectiveness of word clearing as described on the website I linked; merely disambiguating terms will not achieve the same result. $\endgroup$ – Wildcard Oct 7 '17 at 6:42
1
$\begingroup$

There is return value and there are side-effects. The function printf is pure side effect: it puts things to stdout. In general, side effects are things that happen which persist beyond the lifetime of the function. Examples of these include files that are modified or created, or elements of a web page that are modified [JavaScript]. To have a return value, you must have a return statement.

Even in that event, a function might not be a "pure" function. Pure functions must return the same value every time when passed the same parameters. An example that does not meet this criterion: a random number generator.

$\endgroup$
1
$\begingroup$

I don't know your concept of teaching and chronical order of the topics.

What i really love to teach, is the use of the debugger. Show them what is happening in Memory (Stack, etc). If you're using MS Visual Studio or similar tools you can walk through your program by watching your variables and memory.

In an easy understanding (very rudimentary) return handles the passing of a value from a function back to the caller or moving content of one memory address to another.

On the opposite printf shows formatted memory content and/or text to the user on the command line (stdout).

Just for fun and teaching purposes your student can write their own printf function. Not as a varidic one(*), but rather a simplified one that just puts out characters to stdout.

(*) although this could be fun too later on, but including the teaching why these function are risky too in use (memory leaks).

$\endgroup$
  • $\begingroup$ The 2nd paragraph is not clear can you improve it. And welcome to our community. Hope you can continue to contribute. $\endgroup$ – ctrl-alt-delor Oct 5 '17 at 8:59
0
$\begingroup$

The way I learned about programming (Apple Basic, circa 1979) made it clear to me, so I would briefly explain this way:

Procedure - a clump of lines of code that "does something".
Function - an upgraded version: returns a value that can be placed in a variable.
Arguments - values passed in to the function.

Then I would draw a picture of the Stack on the board: a procedure has just a return location, the address where it was called. A function has a return value also, and can have arguments passed on the stack. (Local variables are on the stack too.)

This is a clear visual explanation that entirely explains how all computer programming actually works. And, as a bonus, you get the explanation of how function calls can be nested and return their values up the chain of calls. It is best to tell people the truth, as soon as they need to know it. This is the advice parents get for all situations with their children.

If the students still can't see why printing is different from returning a value, use an analogy: Pushing the Brake pedal in the car does two things: turns on the brake lights (printf), and actually does something - applying the brakes. If you only turn on the brake lights, you will warn the person behind you that you are about to helplessly smack in to something. They will apply their brakes, warning the person behind that... They should get the picture right away. Code has to actually do something. What? Manipulate values stored in memory. How? Show them what memory looks like, and the stack. QED

$\endgroup$
  • $\begingroup$ 1) printing is actually doing something, 2) "Show them what memory looks like, and the stack." sounds just confusing. $\endgroup$ – user253751 Oct 10 '17 at 4:11
0
$\begingroup$

There's basically a single sentence to explain the problem here:

printf() shows you, the user, what's going on, but return shows the computer what's going on.

So how can you illustrate this?

First, I'd begin by having students write aboutprintf() and return and the difference between the two. At least try to get them to be able to say what the difference is.

Next, have students write a simple program without printf() and with return that works - maybe something like (I'm going to use python 3):

def add(a, b):
    return a + b

add(2, 5)

Have them explain why this doesn't return (heh, maybe don't use that term) any errors. Have them explain what return is doing in this case.

Then, another program:

def add(a, b):
    print(a+b)
    return(a+b)

add(2, 5)

Again, have them explain exactly what's going on.

Next:

def add(a, b):
    return(a+b)

print(add(2, 5))

Have them explain what's going on there.

Then move on to more "real" programs and see if this helps. The purpose is just to get them thinking about what they're doing and what the computer's doing. It sounds like something that can be remedied by just making them think.

$\endgroup$
0
$\begingroup$

I'm adding my answer to already huge list of answers, but I believe I'll add something novel, not listed above:

C language semantics is a mess. It is not your students' problem. C is mostly an imperative language, but because the authors didn't care to stick to one paradigm, they introduced a cross-breed of incompatible paradigms (mixing in some declarative style).

In a proper imperative languages like Forth the problem simply doesn't exist. Even remarkably poorly designed Basic and Matlab had solved this problem better: they use the same imperative style, and assign values to some variables. So that you are never tempted to write something like f(g(x)).

You want to solve this problem? Use a better, more consistent language. I understand the need to learn C, as it is an industry standard today, but it would be also wise to invest into the future by educating the students about why this language shouldn't be the industry standard.

$\endgroup$

protected by Community Oct 5 '17 at 17:32

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.