def join(self, experiment, genometric_predicate, output="LEFT", joinBy=None,
             refName="REF", expName="EXP", left_on=None, right_on=None):
        """
        *Wrapper of* ``JOIN``

        The JOIN operator takes in input two datasets, respectively known as anchor
        (the first/left one) and experiment (the second/right one) and returns a
        dataset of samples consisting of regions extracted from the operands 
        according to the specified condition (known as genometric predicate). 
        The number of generated output samples is the Cartesian product of the number 
        of samples in the anchor and in the experiment dataset (if no joinby close 
        if specified). The attributes (and their values) of the regions in the output 
        dataset are the union of the region attributes (with their values) in the input 
        datasets; homonymous attributes are disambiguated by prefixing their name with 
        their dataset name. The output metadata are the union of the input metadata, 
        with their attribute names prefixed with their input dataset name.
        
        :param experiment: an other GMQLDataset
        :param genometric_predicate: a list of Genometric atomic conditions. For an explanation of each of them
                                     go to the respective page.
        :param output: one of four different values that declare which region is given in output 
                       for each input pair of anchor and experiment regions satisfying the genometric predicate:
                       
                       * 'LEFT': outputs the anchor regions from the anchor dataset that satisfy the genometric predicate
                       * 'RIGHT': outputs the anchor regions from the experiment dataset that satisfy the genometric predicate
                       * 'INT': outputs the overlapping part (intersection) of the anchor and experiment regions that satisfy
                         the genometric predicate; if the intersection is empty, no output is produced
                       * 'CONTIG': outputs the concatenation between the anchor and experiment regions that satisfy the 
                         genometric predicate, i.e. the output region is defined as having left (right) coordinates 
                         equal to the minimum (maximum) of the corresponding coordinate values in the anchor and 
                         experiment regions satisfying the genometric predicate


        :param joinBy: list of metadata attributes
        :param refName: name that you want to assign to the reference dataset
        :param expName: name that you want to assign to the experiment dataset
        :param left_on: list of region fields of the reference on which the join must be performed
        :param right_on: list of region fields of the experiment on which the join must be performed
        :return: a new GMQLDataset
        
        An example of usage, in which we perform the join operation between Example_Dataset_1 and Example_Dataset_2
        specifying than we want to join the regions of the former with the first regions at a minimim distance of 120Kb
        of the latter and finally we want to output the regions of Example_Dataset_2 matching the criteria::
            
            import gmql as gl

            d1 = gl.get_example_dataset("Example_Dataset_1")
            d2 = gl.get_example_dataset("Example_Dataset_2")

            result_dataset = d1.join(experiment=d2,
                                     genometric_predicate=[gl.MD(1), gl.DGE(120000)],
                                     output="right")
        """

        if isinstance(experiment, GMQLDataset):
            other_idx = experiment.__index
        else:
            raise TypeError("experiment must be a GMQLDataset. "
                            "{} was provided".format(type(experiment)))
        if isinstance(genometric_predicate, list) and \
            all([isinstance(x, GenometricCondition) for x in genometric_predicate]):
            regionJoinCondition = self.opmng.getRegionJoinCondition(list(map(lambda x: x.get_gen_condition(),
                                                                             genometric_predicate)))
        else:
            raise TypeError("genometric_predicate must be a list og GenometricCondition. "
                            "{} was found".format(type(genometric_predicate)))

        if isinstance(output, str):
            regionBuilder = self.opmng.getRegionBuilderJoin(output)
        else:
            raise TypeError("output must be a string. "
                            "{} was provided".format(type(output)))

        if not isinstance(expName, str):
            raise TypeError("expName must be a string. {} was provided".format(type(expName)))

        if not isinstance(refName, str):
            raise TypeError("refName must be a string. {} was provided".format(type(expName)))

        if isinstance(joinBy, list) and \
            all([isinstance(x, str) for x in joinBy]):
            metaJoinCondition = Some(self.opmng.getMetaJoinCondition(joinBy))
        elif joinBy is None:
            metaJoinCondition = none()
        else:
            raise TypeError("joinBy must be a list of strings. "
                            "{} was found".format(type(joinBy)))

        left_on_exists = False
        left_on_len = 0
        if isinstance(left_on, list) and \
            all([isinstance(x, str) for x in left_on]):
            left_on_len = len(left_on)
            left_on = Some(left_on)
            left_on_exists = True
        elif left_on is None:
            left_on = none()
        else:
            raise TypeError("left_on must be a list of strings. "
                            "{} was provided".format(type(left_on)))

        if isinstance(right_on, list) and \
            all([isinstance(x, str)] for x in right_on) and \
            left_on_exists and len(right_on) == left_on_len:
            right_on = Some(right_on)
        elif right_on is None and not left_on_exists:
            right_on = none()
        else:
            raise TypeError("right_on must be a list of strings. "
                            "{} was provided".format(type(right_on)))

        new_index = self.opmng.join(self.__index, other_idx,
                                    metaJoinCondition, regionJoinCondition, regionBuilder,
                                    refName, expName, left_on, right_on)
        new_local_sources, new_remote_sources = self.__combine_sources(self, experiment)
        new_location = self.__combine_locations(self, experiment)
        return GMQLDataset(index=new_index, location=new_location,
                           local_sources=new_local_sources,
                           remote_sources=new_remote_sources, meta_profile=self.meta_profile)