# Design - Wed, Sep 11

## Announcements

• One piece of paper, front and back, handwritten, is allowed on the midterm. You can write as small as you like, and can bring a magnifying glass.
• You can also use the study guide that will be made for you and printed and waiting for you at your exam seat.
• Check Piazza for more resources.

## Functional Abstractions

``````def square(x):
return mul(x, x)

def sum_squares(x, y):
return square(x) + square(y)``````

What does `sum_squares` need to know about `square`?

• `square` takes one argument.
• `square` computes the square of a number.

What does `sum_squares` not need to know about `square`?

• `square` has the intrinsic name `square`.
• `square` computes the square by calling `mul`.
``````def square(x):              def square(x):
return pow(x, 2)            return mul(x, x-1) + x``````

Even if `square` were bound to a built-in function, `sum_squares` would still work identically. The mechanisms of `square` are irrelevant to `sum_squares`.

## Choosing Names: A Practical Matter

Names should convey the meaning or purpose of the values to which they are bound. The type of value bound to the name is best documented in a function's docstring.

Function names typically convey their effect (`print`), their behavior (`triple`), or the value returned (`abs`).

## Which Values Deserve a Name?

Any of the following reasons makes it fair to add a new name:

• Repeated compound expressions
``````if sqrt(square(a) + square(b)) > 1:
x = x + sqrt(square(a) + square(b))

# avoid repeating sqrt(square(a) + square(b))
hypotenuse = sqrt(square(a) + square(b))
if hypotenuse > 1:
x = x + hypotenuse``````
• Meaningful parts of complex expressions
``````x1 = (-b + sqrt(square(b) - 4 * a * c)) / (2 * a)

# make the quadratic formula easier to discern
discriminant = square(b) - 4 * a * c
x1 = (-b + sqrt(discriminant)) / (2 * a)``````

More tips:

• Names should generally be short and concise, but they can be long if they help document your code.
• `average_age = average(age, students)` is preferable to `aa = avg(a, st)`.
• Names should generally be descriptive, but they can be short if they represent generic quantities: counts, arbitrary functions, arguments to mathematical operations, etc.
• `n, k, i` are usually integers
• `x, y, z` are usually real numbers
• `f, g, h` are usually functions

## Test-Driven Development

Will get back to this.

## Function Example: Sounds

The following is a giant example that takes everything learned so far into account. This is the remainder of today's lecture.

``````from wave import open
from struct import Struct
from math import floor, sin

frame_rate = 11025

def encode(x):
"""Encode float x between -1 and 1 as two bytes.
"""
i = int(16384 * x)
return Struct('h').pack(i)

def play(sampler, name='song.wav', seconds=2):
"""Write the output of a sampler function as a wav file.
"""
out = open(name, 'wb')
out.setnchannels(1)
out.setsampwidth(2)
out.setframerate(frame_rate)
t = 0
while t < seconds * frame_rate:
sample = sampler(t)
out.writeframes(encode(sample))
t = t + 1
out.close()

def tri(frequency, amplitude=0.3):
"""A continuous triangle wave."""
period = frame_rate // frequency
def sampler(t):
saw_wave = t / period - floor(t / period + 0.5)
tri_wave = 2 * abs(2 * saw_wave) - 1
return amplitude * tri_wave
return sampler

c_freq, e_freq, g_freq = 261.63, 329.63, 392.00

# play(tri(c_freq))

c = tri(c_freq)
e = tri(e_freq)
g = tri(g_freq)
low_g = tri(g_freq/2)

def both(f, g):
return lambda t: f(t) + g(t)

# play(both(c, e))
# play(both(c, both(e, g)))
# play(both(c, both(e, g)), seconds=1)

def sampler(t):
seconds = t / frame_rate
if seconds < start or seconds > end:
return 0
elif seconds < start + fade:
return (seconds - start) / fade * f(t)
elif seconds > end - fade:
return (end - seconds) / fade * f(t)
else:
return f(t)
return sampler

# play(note(c, 0, 1/4))
# play(both(note(c, 0, 1/4),
#           note(e, 1/2, 1)))

def mario(c, e, g, low_g):
z = 0
song = note(e, z, z + 1/8)
z += 1/8
song = both(song, note(e, z, z + 1/8))
z += 1/4
song = both(song, note(e, z, z + 1/8))
z += 1/4
song = both(song, note(c, z, z + 1/8))
z += 1/8
song = both(song, note(e, z, z + 1/8))
z += 1/4
song = both(song, note(g, z, z + 1/4))
z += 1/2
song = both(song, note(low_g, z, z + 1/4))
return song

def mario_at(octave):
c = tri(c_freq * octave)
e = tri(e_freq * octave)
g = tri(g_freq * octave)
low_g = tri(g_freq/2 * octave)
return mario(c, e, g, low_g)

play(both(mario_at(1), mario_at(0.5)))``````