more script

This commit is contained in:
Lizzy Hunt 2024-02-05 15:20:34 -07:00
parent b2604a6edb
commit 1e53c846e4
No known key found for this signature in database
GPG Key ID: E835BD4B08CCAF96

172
script.md
View File

@ -62,25 +62,25 @@ def fib(n):
return fib(n - 1) + fib(n - 2)
```
We're in love with its predictability, the assurance that no matter what we give as input, the function behaves in a consistent manner, much like the ideal partner who is reliable and trustworthy.
We're in love with its predictability, the assurance that no matter what we give as input, the function behaves in a consistent manner.
But let's imagine, for a moment, a different kind of relationship. One where actions outside the relationship influence your partner's responses.
Imagine asking your partner about what food they want to eat. But instead of a straightforward answer based on your question alone and some state (i.e. hunger levels, craving ratios, etc), the function's response is influenced by other factors; the day of the week, and the state of the food they ate the day prior.
Imagine asking your partner about what food they want to eat. But instead of a straightforward answer based on your question alone and some state (i.e. hunger levels, craving ratios, etc), the function's response is influenced by other factors; the day of the week, and the state of the day or week prior.
```python
def randomNumber():
def random_number():
return 4 # picked randomly
```
This unpredictability is what side effects introduce into our programming relationships. Where output is not just determined by its input but also by the state of the outside world, or the system at the time of execution. Suddenly, the predictability we cherished is compromised.
This unpredictability is what side effects introduce into our programming relationships. Where output is not just determined by its input but also by the state of the outside world, or the system at the time of execution. Suddenly, the predictability we cherished is compromised. (we'll talk about this more later)
So let's take our love of this function and begin studying it.
We love our black boxes a lot, and we want to share them with other people in our lives. So let's create another that takes a list of people and creates a custom letter for them.
```python
def makeValentinesLetters(people):
def make_valentines_letters(people):
letters = []
for person in people:
letters.append(f"Dear, {person.name}\nYour smile lights up my world. Happy Valentine's Day!")
@ -92,16 +92,16 @@ Good! Now they will all share our love.
But a few months goes by, and soon all their birthdays are coming up! So we make another black box to generate a letter, and in how many days we should give it to them:
```python
def makeBirthdayCards(people):
def make_birthday_cards(people):
today = new Date()
cards = []
for person in people:
daysUntilBirthday = toDays(new Date(person.birthday, today.year) - today)
newAge = today.year - person.birthday.year
days_until_birthday = toDays(new Date(person.birthday, today.year) - today)
age = today.year - person.birthday.year
card = f"Happy Birthday {name}\nI can't believe you're already {newAge} years old!"
cards.append({ "message": card, "deliverInDays": daysUntilBirthday })
card = f"Happy Birthday {name}\nI can't believe you're already {age} years old!"
cards.append({ "message": card, "deliver_in_days": days_until_birthday })
return cards
```
@ -111,10 +111,10 @@ But this is getting annoying; what about Christmas, Thanksgiving, Easter, or ano
What if we create a bunch of black boxes that take a person and create them a card, specifically. Then we create a black box that takes a list of people and another black box and creates a new card for each person?
```python
def valentineLetter(person):
def valentine_letter(person):
return f"Dear, {person.name}\nYour smile lights up my world. Happy Valentine's Day!")
def birthdayCard(person):
def birthday_card(person):
today = new Date()
daysUntilBirthday = toDays(new Date(person.birthday, today.year) - today)
newAge = today.year - person.birthday.year
@ -136,13 +136,151 @@ buildCards(people, birthdayCard)
Functional Reproduction
===
After exploring the simple yet profound beauty of our reliable fibonacci function, our journey into the functional programming romance takes an intriguing turn.
What's better than one black box the uses a black box? Creating more black boxes.
After exploring the simple yet profound beauty of using black boxes in black boxes, our journey into the functional programming romance takes an intriguing turn. What's better than one black box the uses a black box? A black box that creates more black boxes.
All life on Earth contains instructions in the form of DNA. During mitosis, special mechanisms in our cells read the instructions and create new instructions to give to another cell.
Very similarly we can give our black boxes DNA from parents to reproduce and create new black boxes.
Very similarly, we can give our black boxes DNA from parents to reproduce and create new child black boxes. But in our world of black boxes, we call these "closures".
Let's have a look at an example
```
def cardGeneratorFor(person, type):
def personBirthdayCard():
return birthdayCard(person)
def personValentineLetter():
return valentineLetter(person)
if type == "valentine":
return personValentineLetter
if type == "birthday":
return personBirthdayCard
raise NotImplementedError
joseph = {"name": "Joseph", birthday: new Date()}
josephValentineCardGenerator = cardGenerators(joseph, "valentine")
print(josephValentineCard())
```
Here we've created two children with the DNA of cardGenerators; i.e., we get the "person" from the parent black box's DNA.
We can even go a step further:
```
def cardGenerators(person):
def personBirthdayCard():
return birthdayCard(person)
def personValentineLetter():
return valentineLetter(person)
def messageType(type):
if type == "valentine":
return personValentineLetter
if type == "birthday":
return personBirthdayCard
raise NotImplementedError()
return messageType
joseph = {"name": "Joseph", birthday: new Date()}
josephValentineCard = (cardGenerators(joseph))("valentine")
print(josephValentineCards)
```
Wait, do you guys smell that?
Quick Aside: Delicious Curry
===
Sneakily, we've actually started preparing curry!
We've broken up the input of the black box ~cardGenerator~ into it and its child. This is called ~currying~, and it's something we'll touch on in more detail later.
There's another way that we could implement the same functionality - "partial function application"; a fancy name for a simple idea:
```
def josephCard(type):
joseph = {"name": "Joseph", birthday: new Date()}
cardGenerator = cardGeneratorFor(joseph, type)
return cardGenerator()
print(josephCardGenerator("valentine")
```
Immutability
===
We briefly mentioned side effects and alluded them to the unpredictability of a partner. We love our black boxes because they're reliable.
But, we've actually had an impostor among us (AMOGUS sound??) throughout this presentation. Specifically in ~buildCards~:
```
def buildCards(people, cardMaker):
cards = []
for person in people: # side effect (mutation of person)
card = cardMaker(person)
cards.append(card) # side effect (mutation of cards)
# someone could do something nasty with "cards" before it's returned here
return cards
cards = buildCards(people, valentineMaker)
```
This function actually has a small side effect - though it's contained entirely within the function - when we append to the cards list. If we were to go about living a side effect free life, that means we can't change _anything_.
So how do we murder an impostor? With copies and recursion.
```
def build_cards(people, card_maker):
if (len(people) == 0): # base case, no more people to process
return []
# get the first person in the list and make their card
person = people[0]
card = card_maker(person)
rest_people = people[1:] # get sublist of everyone except the first
return [card] + build_cards(rest_people, card_maker)
```
Here we're not changing anything at all (except the stack), with the same functionality; this code is "immutable". When we call ~build_cards~ on a list of people, we're 100% certain we're not gonna get something odd short of a bug.
At a high level there are so many benefits to immutability:
+ Concurrency (TODO: go into more detail)
+ Easier to verify correctness.
+ Compiler optimizations.
+ Easy to read and understand.
But there are also downsides:
+ Keeping immutability requires more computation (data structures)
+ More difficult to write (huge selling point for Object Oriented Programming; encapsulation)
But like all relationships, we need to compromise with our black boxes. Side effects are _unavoidable_ as programmers; when people press the button, they don't want the computation to just exist out there in the computer void. No! They want to see the pop up box, and the value updated on their account. We need to be able to mutate state where the tradeoff of extra compute time is unavoidable like in I/O or a relatively large database.
Writing Code Functionally
===
Determining if a natural number is even or odd in the best way possible is a problem that's stood unsolved since the dawn of time. We'll use this incredibly difficult task to show how we can refactor some code to use the paradigms we just learned.
========
Part Two - A Deeper Dive
===
The Lambda Calculus
===
Completeness of the Lambda Calculus
===
Mention how alonzo church actually failed on his first attempt, natural numbers
Writing a Compiler
===
A Compiler As A Sequence of Transformations
===
Continuation Passing Style
===
=======
Conclusion
===