# chapter=4 
def map(fun, items):
    return (None if is_none(items)
            else pair(fun(head(items)),
                      map(fun, tail(items))))
# In Source, most functions have a fixed number of arguments.
# (The function llist is the only exception, to this so far.)
# The function apply_in_underlying_javascript allows us to
# apply any given function fun to all elements of the argument
# linked list args, as if they were separate arguments
def apply(fun, args):
    return apply_in_underlying_javascript(fun, args)
def apply_generic(op, args):
    type_tags = map(type_tag, args)
    fun = get(op, type_tags)
    return (apply_in_underlying_javascript(fun, map(contents, args))
            if not is_none(fun)
            else error("no method for these types -- apply_generic",
                       llist(op, type_tags)))
def add(x, y): return apply_generic("add", llist(x, y))

def sub(x, y): return apply_generic("sub", llist(x, y))

def mul(x, y): return apply_generic("mul", llist(x, y))

def div(x, y): return apply_generic("div", llist(x, y))
# operation_table, put and get
# from chapter 3 (section 3.3.3)
def assoc(key, records):
    return (None
            if is_none(records)
            else head(records)
            if key == head(head(records))
            else assoc(key, tail(records)))
def make_table():
    local_table = llist("*table*")
    def lookup(key_1, key_2):
        subtable = assoc(key_1, tail(local_table))
        if is_none(subtable):
            return None
        else:
            record = assoc(key_2, tail(subtable))
            return (None
                    if is_none(record)
                    else tail(record))
    def insert(key_1, key_2, value):
        subtable = assoc(key_1, tail(local_table))
        if is_none(subtable):
            set_tail(local_table,
                     pair(llist(key_1, pair(key_2, value)),
                          tail(local_table)))
        else:
            record = assoc(key_2, tail(subtable))
            if is_none(record):
                set_tail(subtable,
                         pair(pair(key_2, value), tail(subtable)))
            else:
                set_tail(record, value)
    def dispatch(m):
        return (lookup if m == "lookup"
                else insert if m == "insert"
                else error("unknown operation -- table", m))
    return dispatch
operation_table = make_table()
get = operation_table("lookup")
put = operation_table("insert")
def attach_tag(type_tag, contents):
    return pair(type_tag, contents)
def type_tag(datum):
    return (head(datum)
            if is_pair(datum)
            else error("bad tagged datum -- type_tag", datum))
def contents(datum):
    return (tail(datum)
            if is_pair(datum)
            else error("bad tagged datum -- contents", datum))
def gcd(a, b):
    return a if b == 0 else gcd(b, a % b)
def install_rational_package():
    # internal functions
    def numer(x): return head(x)
    def denom(x): return tail(x)
    def make_rat(n, d):
        g = gcd(n, d)
        return pair(n // g, d // g)
    def add_rat(x, y):
        return make_rat(numer(x) * denom(y) + numer(y) * denom(x),
                        denom(x) * denom(y))
    def sub_rat(x, y):
        return make_rat(numer(x) * denom(y) - numer(y) * denom(x),
                        denom(x) * denom(y))
    def mul_rat(x, y):
        return make_rat(numer(x) * numer(y),
                        denom(x) * denom(y))
    def div_rat(x, y):
        return make_rat(numer(x) * denom(y),
                        denom(x) * numer(y))
    # interface to rest of the system
    def tag(x):
        return attach_tag("rational", x)
    put("add", llist("rational", "rational"),
        lambda x, y: tag(add_rat(x, y)))
    put("sub", llist("rational", "rational"),
        lambda x, y: tag(sub_rat(x, y)))
    put("mul", llist("rational", "rational"),
        lambda x, y: tag(mul_rat(x, y)))
    put("div", llist("rational", "rational"),
        lambda x, y: tag(div_rat(x, y)))
    put("make", "rational",
        lambda n, d: tag(make_rat(n, d)))
    return "done"

def make_rational(n, d):
    return get("make", "rational")(n, d)

install_rational_package()

r1 = make_rational(1, 3)
r2 = make_rational(2, 5)

print(add(r1, r2))

# expected: [ 'rational', [ 11, 15 ] ]
