David Lambert/j.py

From J Wiki
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)