from math import pi,sin,sqrt;
from functools import partial;

#------------------------------------------------------------------------------#

def fFormat(x):
   return ':{0:.3f}'.format(x);

def output(tpl):
   (v,s)=tpl;
   if s=='': s+='<empty>';
   print('y'+fFormat(v)+' # '+s,'\n');
# end def

#------------------------------------------------------------------------------#

def fSin(x):
   return sin(x);

def fSqrt(x): # Pre: x>=0
   return sqrt(x);
   
def fCompoSinSqrt(x): # Pre: x>=0
   return sin(sqrt(x));

# h(g(f(x))) ?

def fCompo(x,f):
   return f(x);

#------------------------------------------------------------------------------#

def wSin(x):
   y=fSin(x);
   s=fFormat(y);
   return(y,s);

def wSqrt(x): # Pre: x>=0
   y=fSqrt(x);
   s=fFormat(y);
   return(y,s);
   
def wComp_wSin_wSqrt(x): # Pre: x>=0
   (y1,s1)=wSqrt(x);
   (y2,s2)=wSin(y1);
   return (y2,s2+s1);

#------------------------------------------------------------------------------#

def mBind(xs:tuple,wf)->tuple:
   assert isinstance(xs,tuple);
   (x,s1)=xs;
   (y,s2)=wf(x);
   return (y,s2+s1);

def mUnit(x)->tuple:
   return (x,'');

#------------------------------------------------------------------------------#

def mLift(x,f):
   return mUnit(f(x));

def fSquare(x):
   return x*x;

wSquare=partial(mLift,
               f=fSquare);

#------------------------------------------------------------------------------#

def dLift(f):
   def lift(x):
       v=f(x);            
       s=fFormat(v);
       return (v,s);
   return lift;
# end def

@dLift
def dSqrt(x):
   return sqrt(x);

#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#

if __name__ == '__main__':
   
   print('\n# Beginning...');

#------------------------------------------------------------------------------#

   if False:
       x=pi/2; y=wSin(x); output(y);
       x=2; y=wSqrt(x); output(y);
       x=2; y=wComp_wSin_wSqrt(x); output(y);

       x=2; y=fCompo(fCompo(x,fSqrt),fSin); print(y);

#------------------------------------------------------------------------------#

   x=2;
   y=mBind(mBind(mUnit(x),wSqrt),wSin);
   assert(str(y)=="(0.9877659459927356, ':0.988:1.414')");

   y=mBind(mBind(mUnit(x),wSqrt),lambda z : (z*z,fFormat(z*z)));
   assert(str(y)=="(2.0000000000000004, ':2.000:1.414')");

   y=mBind(mBind(mBind(mUnit(x),wSqrt),wSqrt),wSqrt);
   assert(str(y)=="(1.0905077326652577, ':1.091:1.189:1.414')");

#------------------------------------------------------------------------------#

   x=2;
   assert(mBind(mUnit(x),wSqrt)==wSqrt(x));
       # Linksidentität ( x+0=x )

   assert(mBind(mUnit(x),mUnit)==mUnit(x));
       # Rechtsidentität ( 0+x=x )

   y1=mBind(mBind(mUnit(x),wSqrt),wSin);
   y2=mBind(mUnit(x),lambda z : mBind(wSqrt(z),wSin));
   assert(y1==y2);
       # Assoziativität ( (x+y)+z=x+(y+z)=x+y+z )

#------------------------------------------------------------------------------#

   x=2;
   y=wSquare(x);
   assert(str(y)=="(4, '')");

#------------------------------------------------------------------------------#    

   x=2;
   y=mBind(mUnit(x),dSqrt);
   y=mBind(y,wSin);
   assert(str(y)=="(0.9877659459927356, ':0.988:1.414')");

#------------------------------------------------------------------------------#
   print('\n# Finished\n');
# end if main

#------------------------------------------------------------------------------#