Yesterday I wanted to implement function composition and inverse.
The first approach with classes was this (check how to use it within the tests in classes Plus2 and Times5):
class ComposableInversible:
def op(self, *a, **kw):
pass
def rop(self, *a, **kw):
pass
def __invert__(f):
class inverse_f(ComposableInversible):
def op(self, *a, **kw):
return f.rop(*a, **kw)
def rop(self, *a, **kw):
return f.op(*a, **kw)
return inverse_f()
def __or__(outer, inner):
class composed_f(ComposableInversible):
def op(self, *a, **kw):
return outer.op(inner.op(*a, **kw))
def rop(self, *a, **kw):
return inner.rop(outer.rop(*a, **kw))
return composed_f()
# and a basic test I'd reused:
def InversibleTest(Klass):
class _InversibleTest(unittest.TestCase):
class Plus2(Klass):
def op(self, a):
return a + 2
def rop(self, a):
return a - 2
class Times5(Klass):
def op(self, a):
return a * 5
def rop(self, a):
return a / 5
plus2 = Plus2()
times5 = Times5()
def testBasicCase(self):
self.assertEqual(1, (~self.plus2).op(3) )
self.assertEqual(4, (~self.times5).op(20))
def testAFewCases(self):
for x in range(50):
self.assertEqual(
(~(self.plus2 | self.times5)).op(x),
((~self.times5) | (~self.plus2)).op(x))
def testAFewCases2(self):
f = self.plus2 | self.times5
for x in range(0,50):
self.assertEqual(x, (~f | f).op(x))
def testAFewCases3(self):
f = self.times5 | self.plus2
for x in range(0,50):
self.assertEqual(x, (~f | f).op(x))
return _InversibleTest
ComposableInversibleTest = InversibleTest(ComposableInversible)
After that I wanted to eliminate the .op(..) method definition which makes this more obscure, so decided to replace it with call() :
class ComposableInversible2:
__inverse__ = None
__call__ = None
def __invert__(f):
class inverse_f(ComposableInversible2):
def __call__(self, x):
return f.__inverse__(x)
def __inverse__(self, x):
return f(x)
return inverse_f()
def __or__(outer, inner):
class composed(ComposableInversible2):
def __call__(self, x):
return outer(inner(x))
def __inverse__(self, x):
return (~inner)( (~outer)(x))
return composed()
and then:
class ComposableInversible3:
__inverse__ = None
__call__ = None
def __invert__(f):
class inverse_f(ComposableInversible3):
__call__ = f.__inverse__
__inverse__ = f.__call__
return inverse_f()
def __or__(outer, inner):
class composed(ComposableInversible3):
__call__ = lambda _, x: outer(inner(x))
__inverse__ = lambda _, x: (~inner)((~outer)(x))
return composed()
# and the tests (also adapted from the one above):
def makest(klass):
class _thetestklass(unittest.TestCase):
class Plus2(klass):
__call__ = lambda self, x: x+2
__inverse__ = lambda self, x: x-2
class Times5(klass):
__call__ = lambda self, x: x * 5
__inverse__ = lambda self, x: x / 5
plus2 = Plus2()
times5 = Times5()
def testBasicCase(self):
self.assertEqual(1, (~self.plus2)(3) )
self.assertEqual(4, (~self.times5)(20))
def testAFewCases(self):
for x in range(50):
self.assertEqual(
(~(self.plus2 | self.times5))(x),
((~self.times5) | (~self.plus2))(x))
def testAFewCases2(self):
f = self.plus2 | self.times5
for x in range(0,50):
self.assertEqual(x, (~f | f)(x))
def testAFewCases3(self):
f = self.times5 | self.plus2
for x in range(0,50):
self.assertEqual(x, (~f | f)(x))
return _thetestklass
ComposableInversible3Test = makest(ComposableInversible3)
Finally, I started to think that the class-thing makes too much code for something simple and tried a more functional approach..
def F(f,i):
class Klass:
__inverse__ = lambda x: F(i, f)
__call__ = staticmethod(f)
def __invert__(f):
return f.__inverse__()
def __or__(outer, inner):
class composed(Klass):
__call__ = lambda _, x: outer(inner(x))
class __inverse__(Klass):
__call__ = lambda _, x: ((~inner)|(~outer))(x)
return composed()
return Klass()
# a few tests:
class TestF(unittest.TestCase):
plus2 = F(lambda x:x+2, lambda x:x-2)
times5 = F(lambda x:x*5, lambda x:x/5)
def testBasicCase(self):
self.assertEqual(1, (~self.plus2)(3))
self.assertEqual(4, (~self.times5)(20))
def testAFewCases(self):
for x in range(50):
self.assertEqual(
(~(self.plus2 | self.times5))(x),
((~self.times5) | (~self.plus2))(x))
def testAFewCases2(self):
f = self.plus2 | self.times5
for x in range(0, 50):
self.assertEqual(x, (~f | f)(x))
def testAFewCases3(self):
f = self.times5 | self.plus2
for x in range(0, 50):
self.assertEqual(x, (~f | f)(x))
Also I think it would be better something returning both function object and inverse object at once.. but shouldn't be difficult to get that from code above..