Some batteries Python didn't come with


Keywords
piping, pipeline, pipe, functional, python
License
BSD-3-Clause
Install
pip install coppertop==v0.1.2

Documentation

coppertop - some batteries Python didn't come with

(This readme covers coppertop, for bones see here )

As well as some minor batteries that Python didn't come with (a couple of error types, module utils, and sentinals for missing, null, tbc, void, etc), coppertop provides a bones-style aggregation manipulation experience via the following:

  • partial functions
  • piping syntax
  • multiple-dispatch
  • templated algebraic types
  • a std library of protocols
  • immutable updates
  • contextual scope

Partial functions

By 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 syntax

The @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 - takes 1 piped argument and 0+ called arguments

syntax: A >> f(args) -> f(args)(A)

from coppertop.std import anon

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

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

1 >> anon(lambda x: x +1)

binary - takes 2 piped argument and 0+ called arguments

syntax: A >> f(args) >> B -> f(args)(A, B)

from coppertop.core import NotYetImplemented
from coppertop.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 - takes 3 piped argument and 0+ called arguments

syntax: A >> f(args) >> B >> C -> f(args)(A, B, C)

from coppertop.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(_,pystr) >> appendStr(" red balloons go by")
   >> check >> equal >> ???

Multiple-dispatch

Just 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:pyint) -> pyint:
    return x + 1
    
@coppertop
def addOne(x:pystr) -> pystr:
    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]

Templated algebraic type system

As an introduction, consider:

from bones.core.types import BTAtom, S, num, pyint, pystr, O
num = BTAtom.ensure('num')      # nominal
_ccy = BTAtom.ensure('_ccy')    # nominal
ccy = num & _ccy                # intersection
ccy + null                      # union
ccy * pyint * pystr             # tuple (sequence of types)
S(name=pystr, age=num)          # struct
N ** ccy                        # collection of ccy accessed by an ordinal
pystr ** ccy                    # collection of ccy accessed by a python string
(num*num) ^ num                 # (num, num) -> num - a function

I(domestic=ccy&T, foreign=ccy&T)  # named intersection (aka discrimated type)

Example - Cluedo notepad

See algos.py, where we track a game of Cluedo and make inferences for who did it. See games.py for example game input.