David Lambert/j.py
Jump to navigation
Jump to search
Python uses the complex function to create a complex number. Complex appears in the tests. Function names accord with the nuvoc page names. Hence the functions require monadic and dyadic definitions. The md function takes care of this by returning a function that calls the correct function from its arguments depending on the subsequent argument count. Alternatively one could name the uses, such as magnitude and residue for bar.
#! python3 ''' J language Function composition for python programmers ''' import operator # use the builtin operators import functools CAP = None # dual purpose, with not-implemented def monad_or_dyad(monad: callable, dyad: callable) -> callable: ''' Return a callable that chooses which function to call based on the number of arguments Rely on python closures to store the values of monad and dyad. ''' def f(*args): if len(args) == 1: return monad(args[0]) return dyad(args[0], args[1]) return f # md is same as monad_or_dyad expressed in shorthand md = lambda m, d: lambda *args: m(args[0]) if len(args) == 1 else d(args[0], args[1]) def fork(f: callable, g: callable, h: callable) -> callable: ''' return a function that evaluates as would the j language fork according to cap. ''' if f == CAP: monad = lambda y: g(h(y)) dyad = lambda x, y: g(h(x, y)) else: monad = lambda y: g(f(y), h(y)) dyad = lambda x, y: g(f(x, y), h(x, y)) return md(monad, dyad) # fork in shorthand fork = lambda f, g, h: md(lambda y: g(h(y)), lambda x, y: g(h(x, y))) if f is CAP else md(lambda y: g(f(y), h(y)), lambda x, y: g(f(x, y), h(x, y))) hook = lambda f, g: md(lambda y: f(y, g(y)), lambda x, y: f(x, g(y))) tilde = lambda u: md(lambda y: u(y, y), lambda x, y: u(y, x)) at = lambda u, v: md(lambda y: u(v(y)), lambda x, y: u(v(x, y))) slash = lambda u: md(lambda y: functools.reduce(u, y), CAP) star = md(lambda y: y / abs(y) if y else 0, operator.mul) plus = md(lambda y: complex(y.real, - y.imag), operator.add) minus = md(lambda y: - y, operator.sub) percent = md(lambda y: 1 / y, operator.truediv) bar = md(abs, tilde(operator.mod)) idot = md(lambda y: list(range(y)), lambda x, y: x.index(y) if y in x else len(x)) difference_of_squares = fork(plus, star, minus) # (+ * -) assert -41 == difference_of_squares(complex(4, 5)) # monadic assert -9 == difference_of_squares(4, 5) # dyadic mm = fork(CAP, minus, minus) # ([: - -) assert 8 == mm(8) assert 8 == mm(4, 12) sum_of_squares = hook(star, plus) # (* +) assert 25 == sum_of_squares(complex(3, 4)) assert 12 == sum_of_squares(3, 4) assert 25 == at(sum_of_squares, complex)(3, 4) assert 55 == plus(10, slash(plus)(idot(10))) assert 5 == bar(complex(4, 3)) assert 3 == bar(4, 3) assert (3+5j) == tilde(hook(tilde(plus), plus))(3, 5j) # 3 ( +~ + )~ 0j5 left hook assert (3-5j) == hook(plus, plus)(3, 5j) # 3 ( + + ) 0j5 def train(*args): ''' train = lambda *args: args[0] if len(args) == 1 else hook(*args) if len(args) == 2 else train(*(args[:-3] + (fork(*args[-3:]),))) ''' if len(args) == 1: return args[0] if len(args) == 2: return hook(*args) return train(*(args[:-3] + (fork(*args[-3:]),))) t = train(minus, plus, star, minus) # -+*- assert 72 == t(8) assert 70 == t(6,8)