code: def join(left, right, how='inner', key=None, left_key=None, right_key=None,
           join_fn=tuple_join):
      
      if key is None and (left_key is None or right_key is None):
          raise ValueError("Must provide either key param or both left_key and right_key")
  
      if key is not None:
          lkey = rkey = key if callable(key) else make_key_fn(key)
      else:
          lkey = left_key if callable(left_key) else make_key_fn(left_key)
          rkey = right_key if callable(right_key) else make_key_fn(right_key)
  
      try:
          join_impl = {
              "left": _left_join,
              "right": _right_join,
              "inner": _inner_join,
              "outer": _outer_join,
          }[how]
      except KeyError:
          raise ValueError("Invalid value for how: {}, must be left, right, "
                           "inner, or outer.".format(str(how)))
      else:
          return join_impl(left, right, lkey, rkey, join_fn)
docs: :param left: left iterable to be joined
      :param right: right iterable to be joined
      :param str | function key: either an attr name, dict key, or function that produces hashable value
      :param how: 'inner', 'left', 'right', or 'outer'
      :param join_fn: function called on joined left and right iterable items to complete join
      :rtype: list