Package: nonion
NOnionNOnion is a Python package that provides tools for Functional Programming. One of its aims is to eliminate nested function calls such asz(g(f(x)))which remind anonion.InstallingpipinstallnonionTutorialNOnion contains a set of functions and types thatmightsimplify your workflow with Functional Programming in Python. Those tools are designed (but not limited) to work withFunctionandPipelinewrappers.Function- a wrapper ofanyPythonCallable,Pipeline- a wrapper ofanyPythonIterable.It is important to understand thatNOnionprovides tools used for FP in context of Python. Because it is impossible to fully implement some constructs from FP languages in Python,NOnionprovides tools that resemble some of those constructs.FunctionIn order to create aFunction, you simply pass anyCallable:f=Function(lambdax:x+1)f(5)# 6You can also create an identityFunction:g=Function()Notice, that aFunctiontakes exactly single value and returns exactly single value.composeA ``Function composition" defined as $( f \circ g )(x) = f(g(x))$ could be done in the following way:z=f@g# alternativelyz=f.compose(g)You can also usecomposeseveral times:z=f@g@fInstead of wrapping eachCallablewith aFunction, you can wrap onlyfirstCallableand usecomposeon the rest.deff(x):returnx+1g=Function()@(lambdax:x*2)@fg(5)# 12thenFunction composition sometimes might be hard to read, because you have to read it from right-to-left.
In order to achieve better readability, you can usethen.g=Function()/(lambdax:x*2)/fg(5)# 11# alternativelyg=Function().then(lambdax:x*2).then(f)g(5)# 11fanoutIf you need to pass an argument to two functions, you can usefanout:g=Function()/(lambdax:x+1)&(lambdax:x*2)g(5)# (6, 10)mean=(Function()/sum&len)/star(op.truediv)mean([1,2,3])# 2.0# alternativelyg=Function().then(lambdax:x+1).fanout(lambdax:x*2)g(5)# (6, 10)mean=(Function().then(sum).fanout(len)).then(star(op.truediv))mean([1,2,3])# 2.0splitIf you need to apply first value of a pair to a first function and a second value of the pair to a second function, you can usesplit:g=Function()/(lambdax:x+1)^(lambdax:x*2)g((2,3))# (3, 6)teams={"team a":["member 1","member 2"],"team b":["member 3"]}f=Function()/str.capitalize^lenfortinteams.items():print(f(t))# ('Team a', 2)# ('Team b', 1)# alternativelyg=Function().then(lambdax:x+1).split(lambdax:x*2)g((2,3))# (3, 6)f=Function().then(str.capitalize).split(len)fortinteams.items():print(f(t))# ('Team a', 2)# ('Team b', 1)callSometimes you want to call a function ``inline'' after several compositions. In this case, you might use:(Function()/(lambdax:x*2)/f)(5)# 11But it might be hard to read. Especially, when you mostly pass lambdas. A better way to call a function is by using:Function()/(lambdax:x*2)/f|5# 11star(function)Suppose, that you defined a function with multiple arguments such as:deff(x,y):returnx+y*xAnd you want to wrap that function using Function. In this case, you have to usestar.Function()@star(f)|(1,2)# 3starsimply passes arguments to a function using Python*(star) operator.unstar(function)unstaris the opposite function tostar:names=unstar(", ".join)("Haskell Curry","John Smith","George Sand")print(names)# Haskell Curry, John Smith, George SandforeachYou can also call a function for each value in someIterablein the following way:ys=Function()/(lambdax:x*2)/(lambdax:x+1)*range(5)foryinys:print(y)# 1# 3# 5# 7# 9#PipelineIn order to create aPipeline, you simply pass anyIterable:xs=Pipeline(range(5))# notation abuse, do not use that:xs=Function()/Pipeline|range(5)You can also create an emptyPipeline:xs=Pipeline()Under the hoodPipelineis simply usesiteron a passedIterable. It means, that if you will pass anIterable, that could be exhausted, you iterate overPipelineonly once.xs=Pipeline(range(2))forxinxs:print(x)# 1# 2## perfectly fine, because range(x) returns a special objectforxinxs:print(x)# 1# 2#xs=Pipeline(xforxinrange(2))forxinxs:print(x)# 1# 2## xs already exhaustedforxinxs:print(x)mapmapallows you to call aCallable, which takes a single value and returns a single value, on each value of thePipeline.ys=Pipeline(range(3))/(lambdax:x+1)/(lambdax:(x,x+1))/star(lambdax,y:x+y*x)foryinys:print(y)# 3# 8# 15## alternativelyys=Pipeline(range(3)).map(lambdax:x+1).map(lambdax:(x,x+1)).map(star(lambdax,y:x+y*x))filterfilterallows you to filterPipelinevalues.ys=Pipeline(range(3))%(lambdax:x>1)foryinys:print(y)# 2## alternativelyys=Pipeline(range(3)).filter(lambdax:x>1)flatmapflatmapallows you to call aCallable, which takes a single value and returns anIterable, on each value of thePipelineand flatten results into singlePipeline.ys=Pipeline(range(2))/(lambdax:x+1)*(lambdax:(x,x+1))foryinys:print(y)# 1# 2# 2# 3## alternativelyys=Pipeline(range(2)).map(lambdax:x+1).flatmap(lambdax:(x,x+1))applyapplyallows you to call aCallable, which takes anIterableand returns anIterable, on wholePipeline.ys=Pipeline(range(2))/(lambdax:x+1)//tuple# internally Pipeline now has a tupleforyinys:print(y)# 1# 2## now multiple itertations is possibleforyinys:print(y)# 1# 2## alternativelyys=Pipeline(range(2)).map(lambdax:x+1).apply(tuple)collectcollectallows you to call aCallable, which takes anIterableand returns any single value, on wholePipeline. The difference betweenapplyandcollectis thatcollectreturns the result of a function instead of wrapping it withPipeline.ys=Pipeline(range(2))/(lambdax:x+1)>>tupleprint(ys)# (1, 2)## alternativelyys=Pipeline(range(2)).map(lambdax:x+1).collect(tuple)You can also combinecollectwith any function which takes anIterator:ys=Pipeline(range(2))/(lambdax:x+1)>>next_print(ys)# (1,)ys=Pipeline(range(2))%(lambdax:x==5)>>next_print(ys)# ()ys=Pipeline(range(5))>>shift(islice,2)foryinys:print(y)# 0# 1# alternatively you can use applyys=Pipeline(range(5))//shift(islice,2)|print# 0# 1foreachforeachallows you to call aCallable, which takes a single value, on each value of thePipeline.Pipeline(range(2))/(lambdax:x+1)|print# 1# 2## alternativelyPipeline(range(2)).map(lambdax:x+1).foreach(print)groupongrouponis a function which takes a functionCallable[[X], Y], and returns some function which takesIterable[X]and returnsIterable[X]grouped onCallable[[X], Y]function. Thegrouponfunction uses Pythongroupbyfunction under the hood.grouponadds a grouping key using passedCallable[[X], Y]function and sorts values by that key before applyinggroupby.xs=-3,1,0,-1,5(Pipeline(xs)//groupon(lambdax:x>0)/value(tuple)|print)# (False, (-3, 0, -1))# (True, (1, 5))nonion.toolsEitherEitheris a type alias.Eitheris defined as follows:Either=Tuple[Maybe[X],Maybe[Y]]Eithercan be used when you need to return either left (bad) value or a right (good) value:defreadline(path:str)->Either[str,str]:h:Maybe[IOBase]=try_(open)(path)ifnoth:return(("error occurred during open",),())h,*_=hline=h.readline()h.close()return((),line)error,line=readline("requirements.txt")ifline:print(*line)else:print(*error)BecauseEitheris simply a type alias, it does not checks whether only left or only right value is passed.MaybeMaybeis a type alias.Mayberesembles Haskell'sMaybein Python.Maybeis defined as follows:Maybe=Union[Tuple[X],Tuple[()]]As we can seeMaybeis simply sometuplethat might contain a single value or be an emptytuple.
It means that in order to initialize anMaybeyou can simply write:x=()# empty Maybey=(3,)# Maybe with value 3You can easily check whether anMaybeis empty:deff(x:int)->Maybe[int]:return(x,)ifx<3else()x:Maybe[int]=f(5)ifnotx:print("Maybe is empty")# Maybe is emptyYou can also provide an alternative value ifMaybeis empty and immediately try to unwrap theMaybe:x:Maybe[int]=f(5)y,*_=xor(42,)print(y)# 42# alternativelyx:Maybe[int]=f(1)z=xor(42,)# notice: if you pass an empty *z to a single argument function, you will get an errorprint(*z)# 1BecauseMaybeis simply atupleunder the hood, you can apply any Python function (that operates ontuple) to an instance of anMaybe.as_catchas_catchis simply:@currydefas_catch(default:Callable[[X],Y],xys:Iterable[Tuple[X,Y]])->Callable[[X],Y]:returncatch(as_match(xys),default=default)Example ofas_catchusage:successor:Callable[[int],int]=Pipeline(range(10))//zipmapr(lambdax:x+1)>>as_catch(lambda_:-1)print(successor(1))# 2print(successor(100))# -1as_matchas_matchis simply:defas_match(xys:Iterable[Tuple[X,Y]])->Callable[[X],Maybe[Y]]:x_to_y=dict(xys)deflookup(x:X)->Maybe[Y]:return(x_to_y[x],)ifxinx_to_yelse()returnlookupExample ofas_matchusage:successor:Callable[[int],Maybe[int]]=Pipeline(range(10))//zipmapr(lambdax:x+1)>>as_matchprint(successor(1))# (2,)print(successor(100))# ()betweenbetweenis simply:defbetween(low:float,high:float)->Callable[[float],bool]:returnlambdax:low<=xandx<=highExample ofbetweenusage:ys=filter(between(3,5),range(10))print(tuple(ys))# (3, 4, 5)bothbothis a function that takes a functionCallable[[X], Y]and returns a functionCallable[[Tuple[X, X]], Tuple[Y, Y]]. The returned function takes a pair and appliesCallable[[X], Y]to both values.both(lambdax:x+1)((1,2))# (2, 3)cachecacheis a decorator which returns a function that always returns a value that was returned in the first call.deff(x:int)->int:returnx+5g=cache(f)print(g(5))# 10print(g())# 10print(g("abc",1,{}))# 10h=cache(f)print(h(7))# 12catchcatchis a function that resembles pattern-matching in Python. It takes some functions*fs: Callable[..., Maybe[Y]]with some catch-all functiondefault: Callable[..., Y]and returns a functionCallable[..., Y]which executesfsfunctions one by one until some function will return non-emptyMaybe[Y]. If none of those functions will return a non-emptyMaybe[Y], the result ofdefaultfunction is returned.# let's say that we want to parse age ranges that we have in our data:age_ranges=("10-20","20-30","30+","60+","invalid input")# we consider 30+ to be a valid range <30, 100)defparse_range(x:str)->Tuple[int,int]:raw=x.split("-")low,high,*_=map(int,raw)returnlow,highdefparse_unbounded_range(x:str)->Tuple[int,int]:raw,*_=x.split("+")returnint(raw),100# we will use <18, 100) as our default rangeparse=catch(try_(parse_range),try_(parse_unbounded_range),default=lambda_:(18,100))forxinage_ranges:print(parse(x))# (10, 20)# (20, 30)# (30, 100)# (60, 100)# (18, 100)composecomposeis an implementation of a ``Function composition" defined as $( f \circ g )(x) = f(g(x))$.xs="a","ab","c"yxs=enumerate(xs)p:Callable[[Tuple[int,str]],bool]=compose(lambdax:x.startswith("a"),snd)filtered:Iterable[Tuple[int,str]]=filter(p,yxs)ys=map(fst,filtered)print(tuple(ys))# (0, 1)consconsallows you to prepend value of typeXto anIterable[X].print(tuple(cons(1)((2,3,4,5))))# (1, 2, 3, 4, 5)constconstis a function that takes a value of typeXand returns a function of typeCallable[[Y], X]. The returned function will ignore its argument and will return the value that was passed toconst.print(const(1)("abc"))# 1currycurryis simply:defcurry(f:Callable[...,Y])->Callable[...,Y]:returnlambda*args,**kwargs:partial(f,*args,**kwargs)cyclecycleis a function which takesTuple[X, ...]and returnsIterable[X].Iterable[X]is created by repeatedly yielding elements from passedTuple[X, ...].xs=take(10)(cycle([1,2,3]))print(tuple(xs))# (1, 2, 3, 1, 2, 3, 1, 2, 3, 1)dropdropis simply:defdrop(n:int)->Callable[[Iterable[X]],Iterable[X]]:return(lambdaxs:islice(xs,n,None))ifn>0else(lambda_:())Example ofdropusage:xs=drop(1)(range(3))print(tuple(xs))# (1, 2)xs=islice(range(3),1,None)print(tuple(xs))# (1, 2)eithereitheris a function that takes a function of typeCallable[[X], Z]and a function of typeCallable[[Y], Z]and returns a function of typeCallable[[Either[X, Y]], Z]. The returned function takesEither[X, Y]. If theEithercontains left value, the value will be applied toCallable[[X], Z]. If theEithercontains right value, the value will be applied toCallable[[Y], Z]. The result of application is returned.f=either(lambdax:f"Error:{x}",lambday:f"OK:{y}")print(f((("unable to parse",),())))# Error: unable to parseprint(f(((),(1,))))# OK: 1either_to_maybeeither_to_maybeis an alias forsnd.except_except_is a decorator which returns a function that returnsEitherwith some value or anExceptionthat was raised.f=except_(next)xs=iter(range(2))print(f(xs))# ((), (0,))print(f(xs))# ((), (1,))print(f(xs))# ((StopIteration(),), ())failfailis a function which takes a functionCallable[[Exception], Y]and returns a decorator which takes a functionCallable[..., Y]and returnsCallable[..., Y]. The function returned by the decorator uses passedCallable[[Exception], Y]to handle possible errors produced by a decorated function. If no errors produced,Callable[[Exception], Y]will not be executed and the result of the decorated function will be returned.# Let's say that you want to write is_repeated function# which tells you whether you have a collection consisting# only from the single value.# The simplest function you could think of might look like this:defis_repeated(xs:Iterable[X])->bool:x,*rest=xsreturnall(x==yforyinrest)# It works on collections that have at least one value:print(is_repeated((1,1)))# Trueprint(is_repeated((1,2,3)))# False# but when you have an empty collection, this function will result# in an error:print(is_repeated(()))# ValueError: not enough values to unpack (expected at least 1, got 0)# In order to handle this case, you can rewrite this function in a# following manner:defis_repeated(xs:Iterable[X])->bool:xs=iter(xs)wrapped_x=next_(xs)ifwrapped_x:x,*_=wrapped_xreturnall(x==yforyinxs)else:returnTrue# And it would work:print(is_repeated((1,1)))# Trueprint(is_repeated((1,2,3)))# Falseprint(is_repeated(()))# True# You might also use a *fail* function which will surround your# function with try-except clause, to deal with empty collection.@fail(lambda_:True)defis_repeated(xs:Iterable[X])->bool:x,*rest=xsreturnall(x==yforyinrest)# In case when error is raised by is_repeated, the# lambda _: True# function will be executed. The raised error will be passed to# that function.defg(e:Exception)->bool:print(e)returnTrue@fail(g)defis_repeated(xs:Iterable[X])->bool:x,*rest=xsreturnall(x==yforyinrest)print(is_repeated((1,)))# Trueprint(is_repeated(()))# not enough values to unpack (expected at least 1, got 0)# Truefindfindis a function which takes a predicate and returns a function which takes someIterableand returns anMaybewith value that matches the predicate if such value exists:x:Maybe[int]=find(lambdax:x==3)(range(5))print(x)# (3,)x:Maybe[int]=find(lambdax:x==-1)(range(5))print(x)# ()findindexfindindexis a function that works likefind, but instead of returning a function which returns a value inIterablethat matches some predicate, it returns a function which returns an index of that value inIterable.x:Maybe[int]=findindex(lambdax:x==8)(range(5,10))print(x)# (3,)x:Maybe[int]=findindex(lambdax:x==-1)(range(5,10))print(x)# ()findsfindsis a function which takes anIterable[Callable[[X], bool]]of predicates and returns a function which takes someIterable[X]and returns anIterable[Maybe[X]].findsiterates over each predicate and searches for a matching value for that predicate in the passedIterable[X].findswill store checkedIterable[X]values in a buffer, so that the buffer will be checked at first and (if needed) the remainingIterable[X]will be checked at last.fs=(lambdax:x==2),(lambdax:x==4),(lambdax:x==1),(lambdax:x==-1)ys:Iterable[Maybe[int]]=finds(fs)(range(5))foryinys:print(y)# (2,)# (4,)# (1,)# ()flattenlflattenlis a function which takes aTuplewhich contains anotherTupleon the beginning and flattens that innerTupleinside of outerTuple.xys={"A":2.5,"B":3.14}Pipeline(xys.items())//zipr(count(1))/flattenl|print# ('A', 2.5, 1)# ('B', 3.14, 2)flattenrflattenris a function which takes aTuplewhich contains anotherTupleon the end and flattens that innerTupleinside of outerTuple.xys={"A":2.5,"B":3.14}Pipeline(xys.items())//zipl(count(1))/flattenr|print# (1, 'A', 2.5)# (2, 'B', 3.14)flipflipis simply:defflip(f:Callable[[Y,X],Z])->Callable[[X,Y],Z]:returnlambdax,y:f(y,x)Example offlipusage:xs="A","B","C"Pipeline(enumerate(xs))/key(lambdax:x+1)*star(flip(repeat))|print# A# B# B# C# C# Cfoldlfoldlis a function which takes a binary functionCallable[[Y, X], Y]and some accumulatorYand returns a function which takesIterable[X]and returnsY. This function allows you to foldIterable[X]fromleftusing passed binary function. The accumulator is being passed as thefirstargument of the binary function.Example offoldlusage:xs=range(ord("A"),ord("Z")+1)alphabet=Pipeline(xs)/chr>>foldl(operator.add,"")print(alphabet)# ABCDEFGHIJKLMNOPQRSTUVWXYZfoldl1foldl1is a similar function tofoldl. The difference betweenfoldl1andfoldlis thatfoldl1takesCallable[[X, X], X], usesIterable[X]firstelement as the accumulator and returnsX.foldl1will raise an error if the suppliedIterable[X]is empty.foldrfoldris a function which takes a binary functionCallable[[X, Y], Y]and some accumulatorYand returns a function which takesIterable[X]and returnsY. This function allows you to foldIterable[X]fromrightusing passed binary function. The accumulator is being passed as thelastargument of the binary function.Example offoldrusage:xs=range(ord("A"),ord("Z")+1)reversed_alphabet=(Pipeline(xs)/chr//foldr(lambdax,acc:acc+[x],[])>>foldl(operator.add,""))print(reversed_alphabet)# ZYXWVUTSRQPONMLKJIHGFEDCBAfoldr1foldr1is a similar function tofoldr. The difference betweenfoldr1andfoldris thatfoldr1takesCallable[[X, X], X], usesIterable[X]lastelement as the accumulator and returnsX.foldr1will raise an error if the suppliedIterable[X]is empty. Under the hoodfoldr1will usetupleon passedIterable[X]in order to extract the accumulator.fstfstis simply:deffst(xy:Tuple[X,Y])->X:returnxy[0]groupgroupis a function which takesIterable[X]and returnsIterable[Tuple[X, ...]]. This function groups passed elements by equality comparison==.xs=1,1,2,2,2,3,1,1,1print(tuple(group(xs)))# ((1, 1), (2, 2, 2), (3,), (1, 1, 1))groupbygroupbyis a function which takes an equality comparison functionCallable[[X, X], bool]and returns a functionCallable[[Iterable[X]], Iterable[Tuple[X, ...]]]which groups passed elements by the equality comparison function.people=(("Alex",23),("John",23),("Sam",27),("Kate",27),("Fred",23),)grouped=groupby(lambdax,y:snd(x)==snd(y))(people)print(tuple(grouped))# ((('Alex', 23), ('John', 23)), (('Sam', 27), ('Kate', 27)), (('Fred', 23),))# or you can use *on* function:grouped=groupby(on(operator.eq,snd))(people)print(tuple(grouped))# ((('Alex', 23), ('John', 23)), (('Sam', 27), ('Kate', 27)), (('Fred', 23),))in_in_is simply:defin_(xs:Tuple[X,...])->Callable[[X],bool]:returnlambdax:xinxskeykeyis simply:defkey(f:Callable[[X],Z])->Callable[[Tuple[X,Y]],Tuple[Z,Y]]:g:Callable[[Tuple[X,Y]],Z]=compose(f,fst)returnlambdaxy:(g(xy),snd(xy))Example ofkeyusage:xys={"A":[1,2,3],"B":[3,4]}zys=map(key(str.casefold),xys.items())forzyinzys:print(zy)# ('a', [1, 2, 3])# ('b', [3, 4])lengthlengthis a function which takes anIterableand returns number of elements in thatIterable.lengthexhausts theIterable.xs=1,2,3print(len(xs))# 3# len(iter(xs)) will raise an errorprint(length(iter(xs)))# 3liftliftis simply:lift=curry(map)match_match_is a function that resembles pattern-matching in Python. It takes some functions*fs: Callable[..., Maybe[Y]]and returns a functionCallable[..., Maybe[Y]]which executesfsfunctions one by one until some function will return non-emptyMaybe[Y]. If none of those functions will return a non-emptyMaybe[Y], an emptyMaybe[Y](i.e.()) is returned.# let's say that we want to parse age ranges that we have in our data:age_ranges=("10-20","20-30","30+","60+","invalid input")# we consider 30+ to be a valid range <30, 100)defparse_range(x:str)->Tuple[int,int]:raw=x.split("-")low,high,*_=map(int,raw)returnlow,highdefparse_unbounded_range(x:str)->Tuple[int,int]:raw,*_=x.split("+")returnint(raw),100parse=match_(try_(parse_range),try_(parse_unbounded_range))forxinage_ranges:print(parse(x))# ((10, 20),)# ((20, 30),)# ((30, 100),)# ((60, 100),)# ()maybemaybeis a function that takes a function of typeCallable[[], Y]and a function of typeCallable[[X], Y]and returns a function of typeCallable[[Maybe[X]], Y]. The returned function takesMaybe[X]. If theMaybecontains value, the value will be applied toCallable[[X], Y]and a result of application is returned. If theMaybecontains no value, theCallable[[], Y]is called and a result is returned.f=maybe(lambda:"Error",lambdax:f"OK:{x}")print(f(()))# Errorprint(f((1,)))# OK: 1maybe_to_eithermaybe_to_eitheris a function which allows you to create anEither[Y, X]from anMaybe[X].maybe_to_eithertakes a functionCallable[[], Y]and returns a functionCallable[[Maybe[X]], Either[Y, X]].raw_numbers="1\n22\nten\n333".splitlines()xs=(Pipeline(raw_numbers)/try_(int)/maybe_to_either(lambda:f"Failed to parse.")|print)# ((), (1,))# ((), (22,))# (('Failed to parse.',), ())# ((), (333,))The difference between usingmaybe_to_eitherand explicitly creatingEitherusing tuples is thatmaybe_to_eitherwill not evaluate the left part if the right part is present. That is whyCallable[[], Y]is being passed tomaybe_to_eitherinstead ofY.mergemergeis a function which takes two sortedIterable[X]and merges them into single sortedIterable[X]. It useslambda x, y: x <= ycomparison function. Usekeyparameter to specify a functionCallable[[X], Y]to be called on each element prior to making comparisons.xs=merge((1,3,5),(1,2,4))print(tuple(xs))# (1, 1, 2, 3, 4, 5)xs,ys=[(1,"a"),(3,"d"),(5,"f")],[(1,"b"),(2,"c"),(4,"e")]print(tuple((merge(xs,ys,key=fst))))# ((1, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'), (5, 'f'))next_next_is simply:next_:Callable[[Iterator[X]],Maybe[X]]=try_(next)Example ofnext_usage:xs=iter(range(2))print(next_(xs))# (0,)print(next_(xs))# (1,)print(next_(xs))# ()not_not_is a function which takes a predicate and returns negation of that predicate.print(not_(lambdax,y:x==y)(1,5))# Trueononis simply:defon(f:Callable[[Y,Y],Z],g:Callable[[X],Y])->Callable[[X,X],Z]:returnlambdap,n:f(g(p),g(n))Example ofonusage could be found ingroupbysection.padlpadlis a function which allows you to pad someIterablefrom theleftusing a filler. This function takes a numbernand a fillerx. In case whenexact=Falseoption is passed, it returns a function which prependsn - kfillers to the passedIterable, wherekis a length of the passedIterable. In case whenexact=Trueoption is passed, it returns a function which prependsexactlynfillers to the passedIterable.xs="".join(padl(5,"x")("abc"))print(xs)# xxabcxs="".join(padl(5,"x",exact=True)("abc"))print(xs)# xxxxxabcpadrpadris a function which allows you to pad someIterablefrom therightusing a filler. This function takes a numbernand a fillerx. In case whenexact=Falseoption is passed, it returns a function which appendsn - kfillers to the passedIterable, wherekis a length of the passedIterable. In case whenexact=Trueoption is passed, it returns a function which appendsexactlynfillers to the passedIterable.xs="".join(padr(5,"x")("abc"))print(xs)# abcxxxs="".join(padr(5,"x",exact=True)("abc"))print(xs)# abcxxxxxpartitionpartitionis a function which takes a predicate and returns a functionCallable[[Iterable[X]], Tuple[Tuple[X, ...], Tuple[X, ...]]]. This returned function splits passed elements into those that do match the predicate and the rest. The difference betweenspanandpartitionis thatspanstops when it finds the first element that does not match the predicate andpartitiongoes until the end.xs=1,1,2,2,2,3,1,1,1matched,rest=partition(lambdax:x==1)(xs)print(matched)# (1, 1, 1, 1, 1)print(rest)# (2, 2, 2, 3)peekpeekis a decorator which allows you to apply some function to a passed argument and return back the passed argument instead of a function's result.x=peek(print)(5)# 5print(x)# 5xs=(Pipeline(range(3,0,-1))/peek(print,"Countdown:",file=sys.stderr)>>tuple)# Countdown: 3# Countdown: 2# Countdown: 1print(xs)# (3, 2, 1)pickpickis a function which takes some aggregate functionCallable[[Tuple[X, ...]], X]and returns a functionCallable[[Iterable[X]], Iterable[X]]. This returned functionpicksall elements from the passed collectionIterable[X]which are equal to the value returned by the aggregate function. The equal function might be substituted with any other function of type signatureCallable[[X, X], bool]by passing acompareparameter.print(min([1,2,1,1,3,1]))# 1ys=tuple(pick(min)([1,2,1,1,3,1]))print(ys)# (1, 1, 1, 1)print(min([]))# ValueError: min() arg is an empty sequenceys=tuple(pick(min)([]))print(ys)# ()pickbypickbyis a function which takes a functionCallable[[X], Y], an aggregate functionCallable[[Tuple[Y, ...]], Y]and returns a functionCallable[[Iterable[X]], Iterable[X]]. This returned functionpicksall elements from the passed collectionIterable[X]which correspondingYvalues, created by theCallable[[X], Y]function, are equal to the value returned by the aggregate function. The equal function might be substituted with any other function of type signatureCallable[[Y, Y], bool]by passing acompareparameter. The functionCallable[[X], Y]will be used exactly once on the whole collection.cars_and_prices=(("Audi",25000),("BMW",70000),("Mercedes",25000),)cheapest_car=min(cars_and_prices,key=snd)print(cheapest_car)# ('Audi', 25000)cheapest_cars=pickby(snd,min)(cars_and_prices)print(tuple(cheapest_cars))# (('Audi', 25000), ('Mercedes', 25000))powersetpowersetis a function which takes aTuple[X, ...]and produces power set of those elements in form ofIterable[Iterable[X]].xs=tuple(range(3))ps=tuple(map(tuple,powerset(xs)))print(ps)# ((), (2,), (1,), (1, 2), (0,), (0, 2), (0, 1), (0, 1, 2))replicatereplicateis a function which takes a numbernand returns a function, which takes some valuexand repeatsntimes valuex.xs=tuple(replicate(5)("hello"))print(xs)# ('hello', 'hello', 'hello', 'hello', 'hello')reversereverseis a function which takes anIterable[X]and returnsDeque[X]which contains elements fromIterable[X]in reversed order.xs=range(ord("A"),ord("Z")+1)reversed_alphabet=(Pipeline(xs)/chr//reverse# Python reversed would not work on Iterable>>foldl(operator.add,""))print(reversed_alphabet)# ZYXWVUTSRQPONMLKJIHGFEDCBAscanlscanlis a similar function tofoldl. The difference betweenscanlandfoldlis thatscanlinstead of returning a function which takesIterable[X]and returnsY, returns a function which takesIterable[X]and returnsIterable[Y]. The resultingIterable[Y]contains all accumulators used infoldl.xs=scanl(operator.mul,1)((1,2,3,4,5))print(tuple(xs))# (1, 1, 2, 6, 24, 120)scanl1scanl1is a similar function tofoldl1. The difference betweenscanl1andfoldl1is thatscanl1instead of returning a function which takesIterable[X]and returnsX, returns a function which takesIterable[X]and returnsIterable[X]. The resultingIterable[X]contains all accumulators used infoldl1.xs=scanl1(operator.mul)((1,2,3,4,5))print(tuple(xs))# (1, 2, 6, 24, 120)scanrscanris a similar function tofoldr. The difference betweenscanrandfoldris thatscanrinstead of returning a function which takesIterable[X]and returnsY, returns a function which takesIterable[X]and returnsDeque[Y]. The resultingDeque[Y]contains all accumulators used infoldr.xs=scanr(operator.mul,1)((1,2,3,4,5))print(xs)# deque([120, 120, 60, 20, 5, 1])scanr1scanr1is a similar function tofoldr1. The difference betweenscanr1andfoldr1is thatscanr1instead of returning a function which takesIterable[X]and returnsX, returns a function which takesIterable[X]and returnsDeque[X]. The resultingDeque[X]contains all accumulators used infoldr1.xs=scanr1(operator.mul)((1,2,3,4,5))print(xs)# deque([120, 120, 60, 20, 5])searchsearchis a function which takes a predicateCallable[[X], bool]along withIterable[Y]and returns a function which takesIterable[X]and returnsIterable[Y]. This function zipsIterable[Y]withIterable[X]and returns thoseYs for which correspondingXs match the predicate.xs=search(lambdax:x>3,count())(range(1,6))print(tuple(xs))# (3, 4)shiftshiftis a decorator which returns a partially applied function. The difference between Python'sfunctools.partialandshiftis thatshiftwill return a function which prepends*argsand**kwargs:defdummy(*args:object,**kwargs:object):print(args)print(kwargs)partial(dummy,1,2,a=1,b="b")(3,4,c="c")print("-"*10)shift(dummy,1,2,a=1,b="b")(3,4,c="c")# (1, 2, 3, 4)# {'a': 1, 'b': 'b', 'c': 'c'}# ----------# (3, 4, 1, 2)# {'c': 'c', 'a': 1, 'b': 'b'}Example ofshiftusage:take_3=shift(islice,3)xs:Iterable[int]=take_3(range(5))forxinxs:print(x)# 0# 1# 2slideslideis a function which takes a sliding window lengthnand astep, and returns a function which takes anIterableand applies sliding window over it resulting in anIterableoftuples. Eachtuplehas at most length equal ton. In case whenexact=Trueoption is passed, eachtuplehas length equal ton.stepis simply a shift of a sliding window.xs:Iterable[Tuple[int,...]]=slide()(range(10))print(tuple(xs))# ((0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9,))xs:Iterable[Tuple[int,...]]=slide(n=3,step=2)(range(10))print(tuple(xs))# ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8), (8, 9))xs:Iterable[Tuple[int,...]]=slide(n=3,step=2,exact=True)(range(10))print(tuple(xs))# ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8))defis_sorted(xs:Iterable[X],compare:Callable[[X,X],bool]=operator.le)->bool:return(Pipeline(slide(exact=True)(xs))/star(compare)>>all)print(is_sorted((1,2,5)))# Trueprint(is_sorted((1,2,-5)))# Falsesndsndis simply:defsnd(xy:Tuple[X,Y])->Y:returnxy[1]spanspanis a function which takes a predicate and returns a functionCallable[[Iterable[X]], Tuple[Tuple[X, ...], Iterable[X]]]. This returned function splits passed elements into those that do match the predicate on the beginning and the rest.xs=1,1,2,2,2,3,1,1,1matched,rest=span(lambdax:x==1)(xs)print(matched)# (1, 1)print(tuple(rest))# (2, 2, 2, 3, 1, 1, 1)splitatsplitatis a function which takes an indexiand returns a function which splits anIterable[X]intoTuple[X, ...]andIterable[X]. TheTuple[X, ...]will contain firstielements and theIterable[X]will contain the rest.xs,rest=splitat(1)(range(5))print(xs)# (0,)print(tuple(rest))# (1, 2, 3, 4)stripstripis a function which takes anIterable[X]and returns anIterable[X]with removed consecutive duplicates.stripfunctions uses only equality comparison==.xs=1,1,2,2,2,3,1,1,1print(tuple(strip(xs)))# (1, 2, 3, 1)stripbystripbyis a function which takes an equality comparison functionCallable[[X, X], bool]and returns a functionCallable[[Iterable[X]], Iterable[X]]which removes consecutive duplicates in terms of the equality comparison function.people=(("Alex",23),("John",23),("Sam",27),("Kate",27),("Fred",23),)stripped=stripby(lambdax,y:snd(x)==snd(y))(people)print(tuple(stripped))# (('Alex', 23), ('Sam', 27), ('Fred', 23))# or you can use *on* function:stripped=stripby(on(operator.eq,snd))(people)print(tuple(stripped))# (('Alex', 23), ('Sam', 27), ('Fred', 23))taketakeis simply:deftake(n:int)->Callable[[Iterable[X]],Iterable[X]]:return(lambdaxs:islice(xs,n))ifn>0else(lambda_:())Example oftakeusage:xs=take(1)(range(3))print(tuple(xs))# (0,)xs=islice(range(3),1)print(tuple(xs))# (0,)try_try_is a decorator which returns a function that returnsMaybewith some value or an emptyMaybeif anExceptionwas raised.load_json=try_(json.loads)print(load_json("{}"))# ({},)print(load_json("[1, 2, 3]"))# ([1, 2, 3],)print(load_json("abc"))# ()unfoldrunfoldris a function that takes a function of typeCallable[[X], Maybe[Tuple[Y, X]]]and returns a function of typeCallable[[X], Iterable[Y]]. The returned function will repeatedly applyXto the function passed tounfoldruntil it returns value().until_3=unfoldr(lambdaacc:((acc,acc+1),)ifacc<3else())print(tuple(until_3(0)))# (0, 1, 2)valuevalueis simply:defvalue(f:Callable[[Y],Z])->Callable[[Tuple[X,Y]],Tuple[X,Z]]:g:Callable[[Tuple[X,Y]],Z]=compose(f,snd)returnlambdaxy:(fst(xy),g(xy))Example ofvalueusage:xys={"A":[1,2,3],"B":[3,4]}xzs=map(value(len),xys.items())forxzinxzs:print(xz)# ('A', 3)# ('B', 2)wherewhereis a similar function tofindindex. The difference betweenwhereandfindindexis thatwherereturns indices of all elements that match given predicate instead of one. The other difference is thatwherereturns a function which takesIterable[X]and returnsIterable[Y], on the other handfindindexreturns a function which takesIterable[X]and returnsMaybe[int].xs=where(lambdax:x>=8)(range(5,10))print(tuple(xs))# (3, 4)xs=where(lambdax:x==-1)(range(5,10))print(tuple(xs))# ()zipflatlzipflatlis a function which takes a functionCallable[[X], Maybe[Y]], and returns some function which takesIterable[X]and returnsIterable[Tuple[X, Y]]with only those elements fromIterable[X]that are mapped to non-emptyMaybe[Y]by the functionCallable[[X], Maybe[Y]].xs="1","hello","2"f=try_(int)ys=zipflatl(f)(xs)print(tuple(ys))# ((1, '1'), (2, '2'))zipflatrzipflatris a function which takes a functionCallable[[X], Maybe[Y]], and returns some function which takesIterable[X]and returnsIterable[Tuple[Y, X]]with only those elements fromIterable[X]that are mapped to non-emptyMaybe[Y]by the functionCallable[[X], Maybe[Y]].xs="1","hello","2"f=try_(int)ys=zipflatr(f)(xs)print(tuple(ys))# (('1', 1), ('2', 2))zipifzipifis a function which allows you to zipIterable[X]elements withIterable[Y]elements that match a predicateCallable[[X, Y], bool], using a binary functionCallable[[X, Y], Z], intoIterable[Z].When a pairxandydo not match the predicate, a functionCallable[[X], Z]is applied toxand its result is yielded. Also, in the next iteration only the first elementxof the pair will be substituted with it's successorx'andywill remain unchanged (so that the predicate will getx'andy).participants=(("Alex",160.0),("Sam",0.0),("Kate",150.0),("John",155.0),("Fred",35.0))name=fstbalance=sndtickets=((1,160),(2,150),(3,300))ticket_id=fstprice=sndsell_tickets=zipif(lambdauser,ticket:balance(user)>=price(ticket),lambdauser,ticket:(name(user),balance(user)-price(ticket),(ticket,)),lambdauser:(*user,()))forxinsell_tickets(tickets)(participants):print(x)# ('Alex', 0.0, ((1, 160),))# ('Sam', 0.0, ())# ('Kate', 0.0, ((2, 150),))# ('John', 155.0, ())# ('Fred', 35.0, ())ziplziplis simply:defzipl(xs:Iterable[X])->Callable[[Iterable[Y]],Iterable[Tuple[X,Y]]]:returnlambdays:zip(xs,ys)Example ofziplusage:xs="A","B","C"Pipeline(xs)//zipl(count(1))*star(flip(repeat))|print# A# B# B# C# C# Czipmaplzipmaplis simply:defzipmapl(f:Callable[[X],Y])->Callable[[Iterable[X]],Iterable[Tuple[Y,X]]]:returnlambdaxs:map(lambdax:(f(x),x),xs)Example ofzipmaplusage:xs=range(ord("a"),ord("z")+1)upper_to_lower=Pipeline(xs)/chr//zipmapl(str.upper)>>dictPipeline(upper_to_lower.items())//take(5)|print# ('A', 'a')# ('B', 'b')# ('C', 'c')# ('D', 'd')# ('E', 'e')zipmaprzipmapris simply:defzipmapr(f:Callable[[X],Y])->Callable[[Iterable[X]],Iterable[Tuple[X,Y]]]:returnlambdaxs:map(lambdax:(x,f(x)),xs)Example ofzipmaprusage:xs=range(ord("a"),ord("z")+1)upper_to_lower=Pipeline(xs)/chr//zipmapr(str.upper)>>dictPipeline(upper_to_lower.items())//take(5)|print# ('a', 'A')# ('b', 'B')# ('c', 'C')# ('d', 'D')# ('e', 'E')ziprzipris simply:defzipr(ys:Iterable[Y])->Callable[[Iterable[X]],Iterable[Tuple[X,Y]]]:returnlambdaxs:zip(xs,ys)Example ofziplusage:xys={"A":2.5,"B":3.14}Pipeline(xys.items())//zipr(count(1))/flattenl|print# ('A', 2.5, 1)# ('B', 3.14, 2)