def join(
            self,
            inner_enumerable,
            outer_key=lambda x: x,
            inner_key=lambda x: x,
            result_func=lambda x: x
    ):
        """
        Return enumerable of inner equi-join between two enumerables
        :param inner_enumerable: inner enumerable to join to self
        :param outer_key: key selector of outer enumerable as lambda expression
        :param inner_key: key selector of inner enumerable as lambda expression
        :param result_func: lambda expression to transform result of join
        :return: new Enumerable object
        """
        if not isinstance(inner_enumerable, Enumerable):
            raise TypeError(
                u"inner_enumerable parameter must be an instance of Enumerable"
            )
        return Enumerable(
            itertools.product(
                itertools.ifilter(
                    lambda x: outer_key(x) in itertools.imap(
                        inner_key,
                        inner_enumerable
                    ), self
                ),
                itertools.ifilter(
                    lambda y: inner_key(y) in itertools.imap(
                        outer_key,
                        self),
                    inner_enumerable
                )
            )
        )\
            .where(lambda x: outer_key(x[0]) == inner_key(x[1])) \
            .select(
            result_func)