Functions¶

In [ ]:
from byubit import Bit

@Bit.run_from_empty(5, 3)
def go(bit):
    while bit.front_clear():
        bit.move()
        bit.paint('green')

In this example, go is a function.

We define functions using the def keyword.

🎨 def¶

go_green.py¶

def go_green(bit):
    while bit.front_clear():
        bit.move()
        bit.paint('green')
  • go_green is the function name
  • bit is the function argument or parameter
  • The function body is indented 4 spaces (just like with a while or if block)
  • To run or call a function, use the function name followed by parentheses containing the function arguments.
    • e.g. go_green(bit)
In [ ]:
from byubit import Bit


def go_green(cosmo):
    while cosmo.front_clear():
        cosmo.move()
        cosmo.paint('green')

        
@Bit.run_from_empty(4, 3)
def go(bit):
    go_green(bit)
    bit.left()
    go_green(bit)
  • A function parameter serves as another name for whatever is passed to the function.
  • In go, we use bit to refer to Bit
  • In go_green we use cosmo to refer to whatever was passed to it
    • Because we passed bit to go_green, cosmo became another name for bit
    • Try to come up with names that help you remember what the variable actually represents
  • The names a function uses are called its scope

NOTES

  • Draw out the scope of go and the scope of go_green
  • Show how bit points to something on the heap (a bit world)
  • Show how when go_green is called, cosmo now points to that same thing on the heap
  • Show how using the name bit in go_green is different than bit in go
  • Key ideas: variable namespaces, heap, variables pointing to values.
  • Discuss good naming. bit is good, cosmo not so much, thing even worse, color worse still.
In [ ]:
from byubit import Bit


def go_green(bit):
    while bit.front_clear():
        bit.move()
        bit.paint('green')

        
@Bit.run_from_empty(4, 3)
def go(bit):
    go_green
    bit.left()
    go_green

NOTES

Didn't invoke the function: missing parentheses

In [ ]:
from byubit import Bit


def go_green(bit):
    while bit.front_clear():
        bit.move()
        bit.paint('green')

        
@Bit.run_from_empty(4, 3)
def go(bit):
    go_green()
    bit.left()
    go_green()

NOTES

Missing positional argument. The name the error gives you is the name of the parameter in the function you tried to call.

How can we figure out where the problem was? For example, was it the first or second call to go_green?

In coming weeks, we'll talk about other tools that make this easier in complex programs.

For now, you can use bit.snapshot to put breadcrumbs in the program to figure out where the problem is happening.

When naming a function:

  • Use underscore _ (next to the zero key + shift) to break up compound names
    • go_green instead of gogreen or goGreen
  • use lower-case characters
    • go instead of Go or GO
    • Casing matters: go is different from Go!
In [ ]:
from byubit import Bit


def go_green(bit):
    """
    Bit moves in the direction it is already facing
      and goes until it is blocked in front
    Bit paints each square green, 
      except for the starting square
    """
    while bit.front_clear():
        bit.move()
        bit.paint('green')

        
@Bit.run_from_empty(4, 3)
def go(bit):
    go_green(bit)
    bit.left()
    go_green(bit)
  • docstrings allow us to document our functions
  • If the first line of a function is a string, it is used as the docstring
  • The triple quotes """ allow us to include multiple lines in the same string.

snapshot_demo.py¶

NOTES

Gordon Bean:

When our oldest child was just learning how to crawl, my wife and I discovered that our daughter would often get right up behind us without us realizing it. When we would turn around to move to another activity, we would stumble over her. It wasn't a great setup. We were often warning each other that there was a baby right behind. So one day my wife declared: "We need a code word we can use to say 'be careful turning around: there is a baby right behind you'" I agreed that was a good idea, and after a moment's thought she declared "jellyfish".

So, for several years, we've used the term jellyfish to communicate "watch out, there is someone/something right behind you that you might run into as you turn around". It's become second nature. So much, in fact, that I almost used it with a colleague one day as I navigated a crowded room and was passing right behind her.

Human beings have a natural ability for creating ideas ("watch out for the baby right behind you") and giving them names ("jellyfish").

Defining functions is just like that. We get to come up with the idea we want and name it. Then we can use it just like it is an ordinary word.

Functions give you the ability to create and name new ideas.

NOTES

  • Invite students up to the front. Make up a new action (e.g. two high fives and a fist-bump) and give it a name (e.g. "high-bump").
  • Instruct one of the students to "high-bump" the other students, one at a time, by name.
  • Point out that it doesn't make sense to simply "high-bump". You have to "high-bump" someone.
  • We've created a new idea, given it a name, and can apply that new idea to different inputs.
  • One the whiteboard, draw out a "function" named "high-bump" that describes the procedure.

The concept of creating and naming new ideas is called abstraction

Programming is all about the art of abstraction

Blue Ocean 👨🏿‍🎨¶

blue_ocean.py¶

Fill the world with blue.

Bit gives us move, paint, etc.

What new verbs would make this job even easier?

NOTES

We've done problems like this before (nested while, etc.), but this is the first time we are using functions to define the pieces. The emphasis of this exercise is on the decomposition and abstraction of the parts, and then putting those parts together.

Several strategies to consider:

  • Row-by-row, zig-zag
  • Row-by-row, down-and-back
  • Column-by-column
  • Spiral

Which seems easiest? Why?

  • Computers (and humans!) don't do well will uncertainty. We like strategies with 0% uncertainty.
  • When we loop through a block of code, we want to know exactly where Bit will be before and after each iteration.
  • When we call a function, we want to know exactly where Bit will be before and after the function call.

Activity

  • implement go using fill_row_with_blue(bit) (even though it isn't defined yet)
  • Then define and implement fill_row_with_blue

  • What are the boundary conditions of fill_row_with_blue?

    • Where does bit start?
    • Where does bit end?
    • Notice how the prep (turn up) and the down-and-back make the problem easier
In [ ]:
# Solution
from byubit import Bit


def go(bit):
    while bit.front_clear():
        bit.move()

        
def go_blue(bit):
    bit.paint('blue')
    while bit.front_clear():
        bit.move()
        bit.paint('blue')
        
        
def turn_around(bit):
    bit.left()
    bit.left()
    
    
def fill_column_with_blue(bit):
    """
    Bit starts facing up
    Bit turns right and fills the row with blue
    Bit returns to the left and turns to face up
    """
    bit.left()
    go_blue(bit)
    turn_around(bit)
    go(bit)
    bit.left()
    

@Bit.run_from_empty(6, 6)
@Bit.pictures('images/')
def run(bit):

    fill_column_with_blue(bit)
    while bit.front_clear():
        bit.move()
        fill_column_with_blue(bit)
        
    turn_around(bit)
    go(bit)
    turn_around(bit)

Remember¶

  • Choose strategies that minimize uncertainty
  • Pick concise but accurate and helpful names for your functions
  • Document the purpose and boundary conditions of the function
    • What does the function accomplish?
    • Where does Bit start? What direction is he facing?
    • Where does Bit end? What direction is he facing?

Hurdles 👩🏼‍🎨¶

  • What pattern or strategy best fits this problem?
  • What verbs would make this problem easy to solve?

NOTES

The event stream pattern works well: the event is running into a jumpable wall (left clear). The event loop runs until Bit cannot jump anymore.

  • You can also use "up" and "drop" as events with and if/else as opposed to a single "jump hurdle" event
    • But this is a bit tricky: the order of conditions is critical

The mosaic pattern could work: move to the base of the first hurdle, then jump hurdles (which includes advancing to the next) until left is not clear.

  • Approach this using the event-stream pattern, instead of the mosaic pattern used by Stanford

    • Note that the world is slightly different that Stanford's and would require a little setup first (move to base)
  • Note that "up" and "over" are similar, vs "down"

In [ ]:
# solution
from byubit import Bit

def go_green(bit):
    """
    Bit moves forward until blocked, painting green on the way
    The first square is not painted; the last square IS painted.
    """
    while bit.front_clear():
        bit.move()
        bit.paint('green')

        
def cover_green(bit):
    """
    Bit moves forward until the right side is clear, painting green on the way
    The first square is not painted; the last square IS painted.
    """
    while not bit.right_clear():
        bit.move()
        bit.paint('green')
              

def jump_hurdle(bit):
    """
    Bit starts at base of hurdle on left side facing right
    Bit ends at base of hurdle on right side facing right
    """
    # Up
    bit.left()
    cover_green(bit)
    bit.right()
    # Over
    bit.move()
    bit.paint('green')
    cover_green(bit)
    # Down
    bit.right()
    go_green(bit)
    bit.left()

    
@Bit.run('hurdles', 'more-hurdles')
@Bit.pictures('images/')
def run(bit):
    bit.paint('green')
    while bit.left_clear():
        if bit.front_clear():
            bit.move()
            bit.paint('green')
            # go_green(bit)
        else:
            jump_hurdle(bit)
In [ ]:
# another solution
from byubit import Bit

def jump(bit):
    bit.left()
    while not bit.right_clear():
        bit.move()
        bit.paint('green')
    bit.right()
    bit.move()
    bit.paint('green')

def fall(bit):
    bit.right()
    while bit.front_clear():
        bit.move()
        bit.paint('green')
    bit.left()
    
@Bit.run('hurdles', 'more-hurdles')
def run(bit):
    bit.paint('green')
    while bit.left_clear():
        if bit.right_clear():
            fall(bit)

        elif not bit.front_clear():
            jump(bit)
        
        else:
            bit.move()
            bit.paint('green')
            
        

Key Ideas¶

  • functions, def
    • arguments (a.k.a. parameters)
  • abstraction
    • the art of abstraction: learning how to create ideas that are easy to understand and fit together well
  • boundary conditions: fitting the pieces together
  • mosaic pattern: fitting repeated pieces together
  • event-stream pattern: moving towards a goal while performing tasks on cue