<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Monads
Monads are the most feared concept of FP, so I reserve a complete chapter for understanding this concept.
What is a monad?
Right now, my understanding is that monads are a very flexible concept that basically allows to attach context to an otherwise stateless system. This means, that through a monad, the application of a otherwise pure function can be made dependent on context, so that a function will be executed differently in different contexts.
An easy example
Step1:  I now instatiate an instance of this class with a correctly set street attribute in the address dict. Then, everything works well when we want the query the street address from this company
Step2:  However, when we want to get the street name when the company doesn't have a street attribute, this lookup will fail and throw an error
Step3:  What we would normally do to allieviate this issue is to write a function that deals with null values
Step4:  We now see that we are able to complete the request without an error, returning None, if there is no address given or if there is no dict entry for "street" in the address.
But wouldn't it be nice to have this handled once and for all?
Enter the "Maybe" monad!
Step5:  Now, we can rewrite the get_street as get_street_from_company, using two helper function
<ASSISTANT_TASK:>
Python Code:
class Company():
    def __init__(self, name, address=None):
        self.address = address
        self.name = name
        
    def get_name(self):
        return self.name
    
    def get_address(self):
        return self.address
    
Explanation: Monads
Monads are the most feared concept of FP, so I reserve a complete chapter for understanding this concept.
What is a monad?
Right now, my understanding is that monads are a very flexible concept that basically allows to attach context to an otherwise stateless system. This means, that through a monad, the application of a otherwise pure function can be made dependent on context, so that a function will be executed differently in different contexts.
An easy example: The maybe monad
We will start with an easy example: Let's assume we have the task of looking up a street name from a company record. If we'd do it the normal, non-functional way, we'd have to write functions that look up these records and check if the results are not NULL:
This example is heavily inspired by https://unpythonic.com/01_06_monads/
The following is a simple company class, where the address attribute is a simple dict containing the detailed address information.
End of explanation
cp1 = Company(name="Meier GmbH", address={"street":"Herforstweg 4"})
cp1.get_name()
cp1.get_address()
cp1.get_address().get("street")
Explanation: I now instatiate an instance of this class with a correctly set street attribute in the address dict. Then, everything works well when we want the query the street address from this company:
End of explanation
cp2 = Company("Schultze AG")
cp2.get_name()
cp2.get_address().get("street")
Explanation: However, when we want to get the street name when the company doesn't have a street attribute, this lookup will fail and throw an error:
End of explanation
def get_street(company):
    address = company.get_address()
    if address:
        if address.has_key("street"):
            return address.get("street")
        return None
    return None
get_street(cp2)
cp3 = Company(name="Wifi GbR", address={"zipcode": 11476} )
get_street(cp3)
Explanation: What we would normally do to allieviate this issue is to write a function that deals with null values:
End of explanation
class Maybe():
    def __init__(self, value):
        self.value = value
    def bind(self, fn):
        if self.value is None:
            return self
        return fn(self.value)
    def get_value(self):
        return self.value
    
Explanation: We now see that we are able to complete the request without an error, returning None, if there is no address given or if there is no dict entry for "street" in the address.
But wouldn't it be nice to have this handled once and for all?
Enter the "Maybe" monad!
End of explanation
def get_address(company):   
    return Maybe(company.get_address())
def get_street(address):
    return Maybe(address.get('street'))
def get_street_from_company(company):
    return (Maybe(company)
            .bind(get_address)
            .bind(get_street)
            .get_value())
get_street_from_company(cp1)
get_street_from_company(cp3)
Explanation: Now, we can rewrite the get_street as get_street_from_company, using two helper function
End of explanation
<END_TASK>