Package: coppertop-bones
coppertop - piping and multi-dispatch for pythonCoppertop provides a bones-style aggregation manipulation experience in python via the following:partial (application of) functionspiping syntaxmultiple-dispatchtype system with primitives, intersections, unions, products, exponentials, overloads and type schemas thus
allowing Python to be a library implementation language for bonesimmutable updatescontextual scopeand an embryoniccore libraryof common pipeable functionsPartial (application of) functionsBy decorating a function with @coppertop (and importing _) we can easily create partial functions, for example:syntax:f(_, a)->f(_)where_is used as a sentinel place-holder for arguments yet to be confirmed (TBC)from coppertop.pipe import *

@coppertop
def appendStr(x, y):
    assert isinstance(x, str) and isinstance(y, str)
    return x + y

appendWorld = appendStr(_, " world!")

assert appendWorld("hello") == "hello world!"Piping syntaxThe @coppertop function decorator also extends functions with the>>operator
and so allows code to be written in a more essay style format - i.e. left-to-right and
top-to-bottom. The idea is to make it easy to express the syntax (aka sequence) of a solution.unary style - takes 1 piped argument and 0+ called argumentssyntax:A >> f(args)->f(args)(A)from dm.std import anon

@coppertop
def addOne(x):
    return x + 1

1 >> addOne
"hello" >> appendStr(_," ") >> appendStr(_, "world!")

1 >> anon(lambda x: x +1)binary style - takes 2 piped argument and 0+ called argumentssyntax:A >> f(args) >> B->f(args)(A, B)from coppertop.core import NotYetImplemented
from dm.std import each, inject

@coppertop(style=binary)
def add(x, y):
    return x + y

@coppertop(style=binary)
def op(x, action, y):
    if action == "+":
        return x + y
    else:
        raise NotYetImplemented()

1 >> add >> 1
1 >> op(_,"+",_) >> 1
[1,2] >> each >> (lambda x: x + 1)
[1,2,3] >> inject(_,0,_) >> (lambda x,y: x + y)ternary style - takes 3 piped argument and 0+ called argumentssyntax:A >> f(args) >> B >> C->f(args)(A, B, C)from dm.std import both, check, equal

[1,2] >> both >> (lambda x, y: x + y) >> [3,4] >> check >> equal >> [4, 6]as an exercise for the reader[1,2] >> both >> (lambda x, y: x + y) >> [3,4] 
   >> each >> (lambda x: x * 2)
   >> inject(_,1,_) >> (lambda x,y: x * y)
   >> addOne >> addOne >> addOne
   >> to(_,str) >> appendStr(" red balloons go by")
   >> check >> equal >> ???Multiple-dispatchJust redefine functions with different type annotations. Missing annotations are taken as
fallback wildcards. Class inheritance is ignored when matching caller and function signatures.@coppertop
def addOne(x:idx) -> idx:
    return x + 1
    
@coppertop
def addOne(x:str) -> str:
    return x + 'One'
    
@coppertop
def addOne(x):                 # fallback
    if isinstance(x, list):
        return x + [1]
    else:
        raise NotYetImplemented()

1 >> addOne >> check >> equal >> 2
'Three Two ' >> addOne >> check >> equal >> 'Three Two One'
[0] >> addOne >> check >> equal >> [0, 1]Type systemAs an introduction, consider:from ribs.types import BTAtom, S, num, idx, str, O
num = BTAtom.ensure('num')      # nominal
_ccy = BTAtom.ensure('_ccy')    # nominal
ccy = num & _ccy                # intersection
ccy + null                      # union
ccy * idx * str             # tuple (sequence of types)
S(name=str, age=num)          # struct
N ** ccy                        # collection of ccy accessed by an ordinal (N)
str ** ccy                    # collection of ccy accessed by a python string
(num*num) ^ num                 # (num, num) -> num - a function
T, T1, T2, ...                  # type variable - to be inferred at build time

I(domestic=ccy&T, foreign=ccy&T)  # named intersection (aka discrimated type)Example - Cluedo notepadSeealgos.py, where
we track a game of Cluedo and make inferences for who did it. Seegames.pyfor example game input.