module Poly where

------------------------------ Operator syntax and semantics just as before

data Opr = Add | Mul  deriving (Eq,Show)

foldo f g Add = f
foldo f g Mul = g

evalo = foldo (+) (*)
prnto = foldo "+" "*"


------------------------------ Expressions with a single, distinguished variable

data Expr a b = Lit a | Bop b (Expr a b) (Expr a b) | Var  -- deriving (Eq,Show)


----- the fold will now take an extra argument and clause as well

foldx f g h (Lit a)     = f a
foldx f g h (Bop b l r) = g b (foldx f g h l) (foldx f g h r)
foldx f g h  Var        = h


----- printing stays almost the same (just add "x")
----- (let's agree to use integers in the leaves, rather than numerals)

prntx e = foldx show (infx . prnto) "x" e

instance (Show a) => Show (Expr a Opr) where
  show = prntx


----- But evaluation should now produce a *function*

evalx0 (Lit k)     = \n -> k
evalx0 (Bop o l r) = \n -> evalo o (evalx0 l n) (evalx0 r n)
evalx0  Var        = \n -> n


----- this isn't in the form of a fold, but we can re-cast it that way

lift f g h x = f (g x) (h x)

evalx1 (Lit k)     = const k
evalx1 (Bop o l r) = lift (evalo o) (evalx1 l) (evalx1 r)
evalx1  Var        = id

evalx2 = foldx const (lift . evalo) id


----- another (equivalent!) way to do it is to lift the semantics of operators

evalol = foldo (lift (+)) (lift (*))

evalx3 = foldx const evalol id

----- { prove that:   lift . foldo (+) (*)  ==  foldo (lift (+)) (lift (*))   }


----- finally, we have this "cute trick";
----- we start by writing evalx as a function of an expression e and a value n

evalx4 e n = foldx id evalo n e

evalx5 = flip (foldx id evalo)


------------------------------ substitution and application

comp = flip (foldx Lit Bop)

appl = flip (foldx Lit Bop . Lit)

----- (perhaps appl should result in a non-variable style expression ...)


------------------------------ polynomials as (reversed) lists of coefficients

----- poly add and poly multiply

pa = lzw (+) 0
pm cs ds = foldr (\c r -> pa (map (c *) ds) (0 : r)) [] cs

lzw f u []     []     = []
lzw f u (x:xs) []     = f x u : lzw f u xs []
lzw f u []     (y:ys) = f u y : lzw f u [] ys
lzw f u (x:xs) (y:ys) = f x y : lzw f u xs ys



-- wait for it ... wait for it ...












----- polynomial semantics

evalpo = foldo pa pm

evalpx = foldx (:[]) evalpo [0,1]









------------------------------ some handy testing stuff

sampx = Bop Mul (Bop Add Var (Lit 2)) (Bop Add Var (Lit 3))     -- sampx = (x+2) * (x+3)

p x = x^2 + 5*x + 6

test10 f g = and [f x == g x | x <- [0..10]]

test m = test10 (m sampx) p

testall = all test [evalx0, evalx1, evalx2, evalx3, evalx4, evalx5]

testc  = test10 (evalx5 (comp sampx sampx)) (p . p)

testc' = test10 (evalx5 (comp sampx sampx)) (evalx5 sampx . evalx5 sampx)

samps = [ Lit 1, Lit 2, Var, Bop Add Var (Lit 3), Bop Mul (Lit 4) Var, 
          Bop Add Var (Bop Mul (Lit 5) Var), sampx ]

try f = mapM_ (putStrLn . fmt) $ f samps
        where fmt e = "\n" ++ prntx e ++ "\n  =  " ++ prntp (evalpx e)

    --  try id
    --  try (map (`appl` 2))
    --  try (map (comp sampx))
    --  try (scanr1 comp)
    --  try (scanl1 (Bop Add))
    --  try (scanl1 (Bop Mul))


------------------------------ printing for polynomials

infixr 0 ?                          -- read "s ? b" in English as "s when b"

(?) s b = if b then s else ""

showp k (c:cs) = (showp (k+1) cs ++ " + " ? cs/=[]) 
                 ++ (show c ? k==0 || c/=1 )
                 ++ ("x"    ? k>0)
                 ++ ("^" ++ show k ? k>1)

prntp cs = showp 0 cs


------------------------------ helper functions for pretty-printing

infx o l r = par (concat [l,o,r])
prfx o l r = unwords [o,l,r]
pofx o l r = unwords [l,r,o]

par s = "(" ++ s ++ ")"


------------------------------ polynomial for the "comp sampx sampx" example

pp x = x^4 + 10 * x^3 + 42 * x^2 + 85 * x + 72


----- a variation on lzw

lzw' f u xs ys = take n (zipWith f (xs ++ us) (ys ++ us))
                 where n = max (length xs) (length ys)
                       us = repeat u