diff --git a/README.md b/README.md
index a431d0b1cb853ddf30baa5776414626504791a14..dae1df22e109aaea41b09ac09db75d9cee013325 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,6 @@ Applying requires a callable(index: float, value: current value) -> value.
 Joining requires a callable(index: float, valueSelf, valueOther: values from self and other table) -> value.
 
 
-
 ### DiscreteSeries
 
 To use a _DiscreteSeries_ you must give it a set of data to work with. These
diff --git a/firanka/ranges.py b/firanka/ranges.py
index 0d1256bbf9bea1eb49d403000868bc974f65dd65..b0554d43099c472a6a52bd85134f7dfca9fbe6ae 100644
--- a/firanka/ranges.py
+++ b/firanka/ranges.py
@@ -12,7 +12,7 @@ __all__ = [
 ]
 
 
-def _pre_range(fun):
+def _pre_range(fun):  # for making sure that first argument gets parsed as a Range
     @six.wraps(fun)
     def inner(self, arg, *args, **kwargs):
         if not isinstance(arg, Range):
@@ -26,6 +26,7 @@ class Range(object):
     """
     Range of real numbers. Immutable.
     """
+    __slots__ = ('start', 'stop', 'left_inc', 'right_inc')
 
     def translate(self, x):
         if x == 0:
@@ -61,8 +62,8 @@ class Range(object):
             else:
                 args = self.__fromstr(rs)
         elif len(args) == 2:
-            args = args[0], args[1], not math.isinf(args[0]), not math.isinf(
-                args[1])
+            a, b = args
+            args = a, b, not math.isinf(a), not math.isinf(b)
 
         return args
 
@@ -142,12 +143,11 @@ class Range(object):
 
         assert self.start <= y.start
 
-        if (self.stop < y.start) or (y.stop < y.start):
-            return EMPTY_SET
-
-        if self.stop == y.start and not (self.right_inc and y.left_inc):
+        if ((self.stop < y.start) or (y.stop < y.start)) or (
+                self.stop == y.start and not (self.right_inc and y.left_inc)):
             return EMPTY_SET
 
+        # Set up range start
         if self.start == y.start:
             start = self.start
             left_inc = self.left_inc and y.left_inc
@@ -155,6 +155,7 @@ class Range(object):
             start = y.start
             left_inc = y.left_inc
 
+        # Set up range end
         if self.stop == y.stop:
             stop = self.stop
             right_inc = self.right_inc and y.right_inc
@@ -169,6 +170,7 @@ class Range(object):
     def __eq__(self, other):
         if self.is_empty() and other.is_empty():
             return True
+
         return self.start == other.start and self.stop == other.stop and self.left_inc == other.left_inc and self.right_inc == other.right_inc
 
     def __hash__(self):
diff --git a/firanka/series/base.py b/firanka/series/base.py
index efaa6cd4a35c58a8696669dd76fd2ca20c17e003..5be4ebe08b758bf818d011bc5645c0b90e2bd568 100644
--- a/firanka/series/base.py
+++ b/firanka/series/base.py
@@ -68,7 +68,7 @@ class Series(object):
         """
         assert _has_arguments(fun, 2), 'Callable to apply needs 2 arguments'
 
-        return AlteredSeries(self, applyfun=fun)
+        return AlteredSeries(self, fun=fun)
 
     def discretize(self, points, domain=None):
         """
@@ -191,7 +191,19 @@ class DiscreteSeries(Series):
 
         return DiscreteSeries(c, new_domain)
 
+    def join(self, series, fun):
+        if isinstance(series, DiscreteSeries):
+            return self.join_discrete(series, fun)  # same effect
+        else:
+            super(DiscreteSeries, self).join(series, fun)
+
     def join_discrete(self, series, fun):
+        """
+        Very much like join, but it will evaluate only existing discrete points.
+        :param series:
+        :param fun:
+        :return:
+        """
         assert _has_arguments(fun, 3), 'fun must have at least 3 arguments!'
 
         new_domain = self.domain.intersection(series.domain)
@@ -199,21 +211,19 @@ class DiscreteSeries(Series):
         if isinstance(series, DiscreteSeries):
             return self._join_discrete_other_discrete(series, fun)
 
+        def get_both_for_t(t):
+            return fun(t, self._get_for(t), series._get_for(t))
+
+        c = []
         if new_domain.start > self.data[0][0]:
-            c = [(new_domain.start, fun(new_domain.start,
-                                        self._get_for(new_domain.start),
-                                        series._get_for(new_domain.start)))]
-        else:
-            c = []
+            c.append((new_domain.start, get_both_for_t(new_domain.start)))
 
         for k, v in ((k, v) for k, v in self.data if
                      new_domain.start <= k <= new_domain.stop):
             _appendif(c, k, fun(k, v, series._get_for(k)))
 
         if c[-1][0] != new_domain.stop:
-            c.append((new_domain.stop, fun(new_domain.stop,
-                                           self._get_for(new_domain.stop),
-                                           series._get_for(new_domain.stop))))
+            c.append((new_domain.stop, get_both_for_t(new_domain.stop)))
 
         return DiscreteSeries(c, new_domain)
 
@@ -223,16 +233,16 @@ class AlteredSeries(Series):
     Internal use - for applyings, translations and slicing
     """
 
-    def __init__(self, series, domain=None, applyfun=lambda k, v: v, x=0, *args, **kwargs):
+    def __init__(self, series, domain=None, fun=lambda k, v: v, x=0, *args, **kwargs):
         """
         :param series: original series
         :param domain: new domain to use [if sliced]
-        :param applyfun: (index, v) -> newV [if applied]
+        :param fun: (index, v) -> newV [if applied]
         :param x: translation vector [if translated]
         """
         domain = domain or series.domain
         super(AlteredSeries, self).__init__(domain.translate(x), *args, **kwargs)
-        self.fun = applyfun
+        self.fun = fun
         self.series = series
         self.x = x