Making Easter eggs

This part of the course can be hard to understand, until you get used to it. Once it clicks, though, it makes your programming life easier. Easy is good.

Let's start with a silly example. There's a ghost company making Easter eggs. There are three parts to the production process.

  • Get a cube of clay
  • Carve the cube into an egg shape
  • Paint the egg

Here are the three product stages, from left to right.

Product stages

Four ghosts make the eggs. Here they are.

Ghosts

Mainy is in charge. It coordinates production, telling the others when to do their tasks. Mainy doesn't know exeactly what they do. Just what they need to do their job, and what the result of their work is.

The others are:

  • Gety gets a cube from a magic cube making machine. Needs: nothing. Returns: a cube.
  • Carvey carves a cube into a blank egg shape. Needs: a cube. Returns: a blank egg.
  • Painty paints the blank egg. Needs: a blank egg. Returns: a painted egg.

Gety, Carvey, and Painty are specialists. They each do one function. Mainy gives them a thing (except Gety, who starts with nothing). They do something to the thing they get, transforming it. Then they take the transformed thing back to Mainy.

Mainy's job is to give each ghost what they need, send them to work, and take what they make. Then pass it on to the next ghost.

Adela
Adela

Why didn't the ghost go to the dance?

Ethan
Ethan

Why?

Adela
Adela

It had nobody to go with.

Ethan
Ethan

Ha! My dad would tell that joke. Unfortunately.

Mainy's program

Here's the program Mainy runs:

  • "Hey Gety! Get a cube." Gety goes away, does something, and returns with a cube.
  •  
  • "Hey Carvey! Here's a cube. Carve a blank egg." Carvey takes the cube, does something, and returns with a blank egg.
  •  
  • "Hey Painty! Paint this blank egg." Painty takes the cube, does something, and returns with a painted egg.

We could make this Pythony. Here's the first line:

  • thing = get_cube() # Give Gety nothing, get back a thing.

get_cube is the name of a chunk of code, called a function, that Gety runs. The parens list what Mainy gives Gety. Gety doesn't need anything from Mainy to do its job.

Remember how = works. The thing on the right goes into the thing on the left. So this...

  • price = 5.99

... puts 5.99 (the thing on the right) into the variable price (the thing on the left).

So this...

  • thing = get_cube() # Give Gety nothing, get back a thing.

... puts whatever get_cube returns into thing.

Let's continue with Mainy's program.

  • # Give Gety nothing. Wait. Get back a thing.
  • thing = get_cube()
  • # Give Carvey the thing Gety returned. Wait. Get whatever Carvey returns.
  • new_thing = carve_cube(thing)
  • # Give Painty the thing Carvey returned. Wait. Get whatever Painty returns.
  • finished_thing = paint_egg(new_thing)

That's the task from Mainy's point of view. It doesn't need to know how the tasks get done. It only cares about what it gives each function (each ghost), and what it gets back.

Mainy is a coordinator. It makes sure the right subtasks get done in the right order.

Here's a messed up program for Mainy.

  • thing = get_cube()
  • new_thing = paint_egg(thing)
  • finished_thing = carve_cube(new_thing)

The cube is painted, then carved. Hmm. No good. Even though Gety, Carvey, and Painty all did their jobs correctly, it's a hard fail. The coordination was broken.

Ethan
Ethan

What helps a ghost lie perfectly flat?

Ray
Ray

What?

Ethan
Ethan

A spirit level!

get_cube()

Let's see things from Gety's point of view. It hangs around waiting for an instruction from Mainy.

Get a cube!

Gety goes and does that job. Just that. It doesn't know how to do anything else.

Here's Gety's code.

  • def get_cube():
  •     Go to the cube making machine
  •     Twist a lever
  •     Flip a button
  •     Press a dial
  •     Grab the completed cube
  •     return cube

Here's Gety, having just arrived at the machine.

Just arrived

Gety works away. Here it is with the cube, getting ready to go back to Mainy.

Gety has the cube

Gety carries the cube back to Mainy, and hands it over. Gety's doesn't know what Mainy does with the cube. That's not its job.

Now Mainy has the cube.

Mainy has the cube

Here's all the code so far.

  • def get_cube():
  •     Go to the cube making machine
  •     Twist a lever
  •     Flip a button
  •     Press a dial
  •     Grab the completed cube
  •     return cube
  •  
  • thing = get_cube()
  • # new_thing = carve_cube(thing)
  • # finished_thing = paint_egg(new_thing)

get_cube is a function, a block of code with a name. This one sends cube back to whoever called it. In geek language, it returns cube.

I commented out the last two lines, since we don't have the functions yet.

carve_cube(thing)

Let's work on the line:

  • new_thing = carve_cube(thing)

Mainy hands the cube to Carvey, and tells it to get to work.

Carvey has the cube

Carvey goes to its carving workstation, and gets started.

Carvey at work

Here's Carvey's code.

  • def carve_cube(thing):
  •     Chop top left off
  •     Chop top right off
  •     Chop bottom left off
  •     Chop bottom right off
  •     Round the middle
  •     Grind edges
  •     Sand smooth
  •     Throw away chunks
  •     Throw away dust
  •     Clean workstation
  •     return thing

Another function. A chunk of code with a name (carve_cube), that takes something in (thing), and returns something, that is, sends something back, in this case a changed thing.

Here's Carvey taking the egg to Mainy.

Carvey returning blank egg

Carvey doesn't know what Mainy does with the blank egg. Not its job.

From the outside (Mainy's view), Carvey gets a thing, does stuff, and returns a transformed thing. Mainy doesn't know the deets.

Now Mainy has the blank egg.

Mainy has the blank egg

Here's the code so far.

  • def get_cube():
  •     Go to the cube making machine
  •     Twist a lever
  •     Flip a button
  •     Press a dial
  •     Grab the completed cube
  •     return cube
  •  
  • def carve_cube(thing):
  •     Chop top left off
  •     Chop top right off
  •     Chop bottom left off
  •     Chop bottom right off
  •     Round the middle
  •     Grind edges
  •     Sand smooth
  •     Throw away chunks
  •     Throw away dust
  •     Clean workstation
  •     return thing
  •  
  •  
  • thing = get_cube()
  • new_thing = carve_cube(thing)
  • # finished_thing = paint_egg(new_thing)
Ray
Ray

My ghost buddy seems sad and depressed lately.

Georgina
Georgina

Why?

Ray
Ray

It's been going through some things.

paint_egg(thing)

Last step. Mainy gives the blank egg to Painty, and tells it to do its thing.

Mainy gives Painty the egg

Painty goes to its paintery, and gets to work.

Painty ready to work

Paint, paint, paint...

Here's its program:

  • def paint_egg(egg):
  •     Wash egg
  •     Primer
  •     Base
  •     Pattern
  •     Lacquer
  •     Buff
  •     Clean work area
  •     return egg

When Painty is done, it takes the painted egg back to Mainy.

Painty returning egg

Painty hands the egg over to Mainy.

Ready to ship

The easter egg is done!

Here's the final program.

  • def get_cube():
  •     Go to the cube making machine
  •     Twist a lever
  •     Flip a button
  •     Press a dial
  •     Grab the completed cube
  •     return cube
  •  
  • def carve_cube(thing):
  •     Chop top left off
  •     Chop top right off
  •     Chop bottom left off
  •     Chop bottom right off
  •     Round the middle
  •     Grind edges
  •     Sand smooth
  •     Throw away chunks
  •     Throw away dust
  •     Clean workstation
  •     return thing
  •  
  • def paint_egg(egg):
  •     Wash egg
  •     Primer
  •     Base
  •     Pattern
  •     Lacquer
  •     Buff
  •     Clean work area
  •     return egg
  •  
  • # Main program.
  • thing = get_cube()
  • new_thing = carve_cube(thing)
  • finished_thing = paint_egg(new_thing)

Each function is one step in a longer process. The main program coordinates. It calls the functions in the right sequence, sending them what they need, and taking what they send back.

This is how Python is often written. Functions at the top, main program below. All lines of the main program are kept together. That's how we'll do all our code from now on.

Course standard

Functions at the top, main program below. All lines of the main program are kept together.

Adela
Adela

A question. The variable names aren't the same. Like this:

  • def paint_egg(egg):
  •     Wash egg
  •     ...
  •     Clean work area
  •     Return egg
  •  
  • # Main program.
  • ...
  • finished_thing = paint_egg(new_thing)

The blank egg is called new_thing in the main program, but egg in the function. What's the haps, daddy-O?

Ooo, good eye, Adela, and good use of archaic slang. The things in () are called parameters, or arguments (more later). It doesn't matter so much what they're called. The first parameter in the call paint_egg(new_thing) is matched up to the first parameter in the definition of paint_egg(egg).

Why do this? It lets you reuse the function with different variables. Imagine you had three eggs being processed at the same time. You could have this code:

  • ...
  • blank_egg1 = carve_cube(cube1)
  • blank_egg2 = carve_cube(cube2)
  • blank_egg3 = carve_cube(cube3)
  • finished_egg1 = paint_egg(blank_egg1)
  • finished_egg2 = paint_egg(blank_egg2)
  • finished_egg3 = paint_egg(blank_egg3)

carve_cube is called three times, with three different cubes passed in. We don't have to write carve_cube three times. Write it just once. We can reuse it, sending in different cubes.

This doesn't change Carvey's work. It doesn't matter which cube it gets, it always does the same thing: carve it.

The same for paint_egg. Write the code once, call it three times, passing in a different blank egg each time. The same thing happens for each egg. It gets painted.

Big productivity win!

Businesses care a lot about cost control. Functions are the main cost control tool in programming.

Geekery

Here's the code again.

  • def paint_egg(egg):
  • Wash egg
  • ...
  • Clean work area
  • return egg
  • # Main program.
  • ...
  • finished_thing = paint_egg(new_thing)

Remember, a variable is really a name for a memory slot. Each memory slot has an address, a strange thing like 34ED93F. In the main program, the variable new_thing points to something like 34ED93F.

When the main program runs paint_egg(new_thing), it's really saying,

"Hey, paint_egg, do your stuff, with the contents of the memory slot 34ED93F."

When paint_egg runs, inside itself it uses the name egg for memory slot 34ED93F. It's working on the data the main program sent.

So that's why parameter names in calls to functions (in the main program) and function definitions can be different.

It's actually more complex than that, but this will give you a general idea of how it works.

Writing the program from scratch

When someone gives you code and explains it, you might think:

"Yo, I got that, dawg. No worries."

Writing it yourself is harder.

Say you had to write the entire program for the ghosts. Where to start?

The biggest problem with programming is programs get too big to think about all at once. Here's the code again.

  • def get_cube():
  •     Go to the cube making machine
  •     Twist a lever
  •     Flip a button
  •     Press a dial
  •     Grab the completed cube
  •     return cube
  •  
  • def carve_cube(thing):
  •     Chop top left off
  •     Chop top right off
  •     Chop bottom left off
  •     Chop bottom right off
  •     Round the middle
  •     Grind edges
  •     Sand smooth
  •     Throw away chunks
  •     Throw away dust
  •     Clean workstation
  •     return thing
  •  
  • def paint_egg(egg):
  •     Wash egg
  •     Primer
  •     Base
  •     Pattern
  •     Lacquer
  •     Buff
  •     Clean work area
  •     return egg
  •  
  • # Main program.
  • thing = get_cube()
  • new_thing = carve_cube(thing)
  • finished_thing = paint_egg(new_thing)

How many lines? One, two, three... a lot. Brains are good at many things, but keeping track many of details at once, not so much.

Here's the trick:

  • Break a task into chunks
  • Do one chunk at a time

Here are three developer ghosts. Pinky, Blocky, and Gothy.

Devs

Let's have the Pinkster write the program. We'll get to the others later. Let's listen to Pink's thinks. You can do that with ghosts.

- - ENTER GHOST BRAIN SCAN MODE - -

Yo, broski, gotta write me a program to make them Easter eggs. Lots going on here, Brobafett. Gotta break it down.

I'll write the main brogram first.

Pinky's on track

That's the right way to begin. Think about the big chunks first. Do not get bogged down in details.

So, gonna do dis:

  • Get a cube
  • Carve the cube into a blank egg
  • Paint it

The Pink is still on track

P-bro didn't write Python. It listed the main steps in plain language. A good thing.

Yo bro, I like that. We know how to get a cube. We know how to carve one. We know how to paint a blank egg.

That should work.

Pster thinks the steps can be done

Pinky asks itself whether the company can do each step. It thinks so, so the list should work.

OK. Next. Hmm... I'll make it Brothon.

  • # Main program.
  • # Get a cube.
  • cube = get_cube()
  • # Carve it into a blank egg.
  • blank_egg = carve_cube(cube)
  • # Paint it.
  • painted_egg = paint_egg(blank_egg)

Broda says: Always write the main program first.

Listen to Broda, er, Yoda

Always write the main program first in this course.

Maybe professionals do it differently sometimes, but usually they start with the main program, too. Since you're learning, always start with the main program.

OK me-bro, write get_cube(). Don't think about carving or painting. Just think about making cubes.

Go to the machine, operate it, get the cube... return it to the main brogram.

(Typey type type, write the code)

The last line of the function is return cube. There. I gots the bro-how!

How did it know?

How did Mr P - I pity da bro! - know to make get_cube return the cube as the last line? Because that's what the main program wanted: cube = get_cube(). For that line to work, get_cube must return something.

Yo bro, the next line in the main brogram is...

  • blank_egg = carve_cube(cube)

I'll write the first and last lines.

  • def carve_cube(cube):
  • return blank_egg

Note

The call in the main program...

  • blank_egg = carve_cube(cube)

... tells the Pster what the first and last lines of the function have to be. This part of the call in the main program...

  • blank_egg = carve_cube(cube)

... names the function and says it has one thing passed in. That means the first line has to be...

  • def carve_cube(cube):

The param could be called something else (remember Adela asked about different names?), but cube is convenient.

This part of the call in the main program...

  • blank_egg = carve_cube(cube)

... means the last line of the function has to be...

  • return blank_egg

Well, it doesn't have to be the last line, as we'll see later. For our function, it makes sense for it to be the last line.

Gots the first and last lines. Write the rest...

(Typey type type type. Sir Pinkster writes the rest of the function.)

Yo, I'm a real bro-getter! Now for the last function. The main brogram calls it like this:

  • # Main program.
  • ...
  • # Paint it.
  • painted_egg = paint_egg(blank_egg)

The function has to be called paint_egg, and gets sent one thing. The function returns a thing, too, so the main brogram can put in into a variable. So, the first and last lines are:

  • def paint_egg(blank_egg):
  •     return painted_egg

Wrote the rest... (typey type type type type... The Glorious Pink writes the function)

- - EXIT GHOST BRAIN SCAN MODE - -

Write programs like your bromigo Pinky. Write the main program first. It has calls to functions. Then write the functions.

The other ghosts

There were other ghosts:

Devs

Let's say they're part of the same team, with Pinky in charge (not a good idea, but that's the way it is). Instead of Pinky writing the entire program, they're going to work on it at the same time, to get it done faster.

The goal: each ghost writes part of the program. They work on the same program at the same time, with each ghost working on different parts of it. They put the parts together, and the whole program works.

For this to work, they need to coordinate their code, so it will all fit together. Functions make that possible.

Let's listen in on a planning meeting.

- - MEETING BEGINS - -

Yo bros, let's write some brolicious code. Here's the main brogram:

  • # Main program.
  • # Get a cube.
  • cube = get_cube()
  • # Carve it into a blank egg.
  • blank_egg = carve_cube(cube)
  • # Paint it.
  • painted_egg = paint_egg(blank_egg)

I'll write get_cube, no params in, one value out.

Blocky, you write carve_cube. One param in, a cube. One value out, a blank egg.

Gothy, you write paint_egg. One param in, a blank egg. One value out, a painted egg.

OK, team, let's do it! Remember, be broactive!

- - MEETING ENDS - -

Blocky knows to write a function called carve_cube, with one param in, and one value out. As long as Blocky does that, its code will work with the rest of the program.

Gothy knows to write a function called paint_egg, with one param in, and one value out. As long as Gothy does that, its code will work with the rest of the program.

A function's name, params, and return type are called its signature. As long as everyone knows their functions signature, and what their function does, the can all work at the same time. When everyone is done, Pinky can assemble all the parts together to get a working program.

Note

Functions let people write pieces of a program at the same time. The program gets finished faster.

What's next?

Leave ghost world for now. Let's see a more humany explanation of functions. Reading different descriptions of the same thing helps your brain make a complete picture.