// file:    examples.C
// author:  Robert Keller
// purpose: Examples of using the Polya C++ library
// compile: g++ examples.C polya.o
// See polya.html for documentation.

#include "polya.H"
#include <fstream.h>
#include <strstream.h>

// forward prototypes

Poly square(Poly x);
Poly scale(Poly k);
Poly list2(Poly a, Poly b);
Poly isOdd(Poly x);
Poly greaterThan5(Poly x);
Poly list2(Poly a, Poly b);
Poly precat(Poly x);
Poly upto(Poly n);
Poly applyto(Poly f);
Poly addTag(Poly a);


// Making an Oclosure for a Curried add functoin

class occ1 : public Applicable
{
private:
Poly arg1;

public:
occ1(Poly arg1) 			// constructor
  {
  this->arg1 = arg1;
  }

Poly operator()(Poly arg2) const 	// applier
  {
  return cat(arg1, arg2);
  }
};


void examples()
{
cout << "Three ways of getting the empty list: "
     << nil << NIL << list() << endl;

cout << "Constructing a list by enumerating its elements: "
     << list(1, 2, 3) << endl;

cout << "Constructing a heterogenous list: "
     << list(1, 2, "buckle", "my", "shoe") << endl;

cout << "Setting a variable to a list: ";
Polylist L = list(1, 2, 3);
cout << L << endl;

cout << "Constructing a list using cons: "
     << cons(0, L) << endl;

cout << "Constructing a list with lists as elements: "
     << "list of lists: " << list(list(1, 2, 3), list(4, 5), list(6)) << endl;

cout << "Reversing a list (non-destructively): "
     << L.reverse() << endl;

cout << "Using functional syntax: "
     << reverse(L) << endl;

cout << "Appending one list to another (non-destructively): ";
Polylist M = list("x", "y", "z");
cout << L.append(M) << endl;

cout << "Appending using ^ operator: ";
cout << (L ^ M ^ L) << endl;

cout << "Generating a range list: ";
L = range(0, 9);
cout << L << endl;

cout << "Using word selectors: "
     << first(L) << second(L) << third(L) << fourth(L) << fifth(L)
     << sixth(L) << seventh(L) << eighth(L) << ninth(L) << tenth(L) << endl;

cout << "Using rest: " 
     << L.rest() << endl;

cout << "Using numeric selectors, length: "
     << L[0] << L[1] << L[2] << L[3] << L[4] << L[L.length()-1] << endl;

cout << "Testing membership: "
     << member(5, L) << member(10, L) << endl;

cout << "Prefixing: "
     << L.prefix(5) << endl;

cout << "Mapping: "
     << L.map(square) << endl;

cout << "Keeping odd: "
     << L.keep(isOdd) << endl;

cout << "Dropping odd: "
     << L.drop(isOdd) << endl;

cout << "Finding > 5: "
     << L.find(greaterThan5) << endl;

cout << "Association list: ";
Polylist alist = list(list("a", "alpha"), list("e", "epsilon"), 
                      list("i", "iota"),  list("o", "omicron"), 
                      list("u", "upsilon"));
cout << alist.assoc("o") << alist.assoc("z") << endl;

cout << "Reducing a list by 'folding': " << endl;
Polylist args = list("a1", "a2", "a3", "a4", "a5", "a6", "a7");
cout << "  foldl: " << args.foldl(list2, "unit") << endl
     << "  foldr: " << args.foldr(list2, "unit") << endl;

// user-defined list2 is used because list is overloaded and would be ambiguous


cout << "Accumulated reduction using scan operators:" << endl
     << "  scanl: " << L.scanl(cat, "") << endl
     << "  scanr: " << L.scanr(cat, "") << endl;


cout << "Imploding a list to a string: " << L.implode() << endl;

cout << "Reading characters from a file: " << endl;
ifstream input("test.in");
cout << Polylist::inchars(input).implode() << endl;


cout << "The empty array: " 
     << array() << endl;

cout << "An array with specific elements: "
     << array(1, 2, 3, "foo", "bar") << endl;

cout << "Setting an array variable: ";
Polyarray A = array(1, 2, 3, "foo", "bar");
cout << A << endl;

cout << "Setting array elements from a list: ";
A = L;
cout << A << endl;

cout << "Mapping over an array: "
     << A.map(square) << endl;


cout << "Indexing an array, length of an array: "
     << A[0] << A[A.length()-1] << endl;

cout << "Mapping using a higher-order function (scale): " << endl;
cout << "  scaled by 2: " << L.map(scale(2)) << endl;
cout << "  scaled by 3: " << A.map(scale(3)) << endl;

cout << "Printing a function closure: " << scale(2) << endl;

cout << "Reversing array in place: ";
A.reverse();
cout << A << endl;

cout << "Sorting array in place: ";
A.sort();
cout << A << endl;

cout << "Resizing array in place: " << endl;
A.resize(5);
cout << A << endl;
A.resize(10);
cout << A << endl;

cout << "Making arrays and lists using a function: " << endl
     << Polylist::make(square, 10) << endl
     << Polyarray::make(square, 10) << endl;


cout << "Testing for equality, etc.: " << endl;
cout << "These should give 0: "
     << (list(1, 2, 3) == list(3, 2, 1))
     << (array(1, 2, 3) < array(1, 2, 3))
     << (list(1, 2, 3) < list(1, 2, 3)) 
     << endl;
cout << "These should give 1: "
     << (nil == array())
     << (list(1, 2, 3) == array(1, 2, 3))
     << (list(1, 2, 3) < list(2, 3, 4))
     << (array(1, 2, 3) < list(1, 2, 4))
     << (array(1, 2, 3) < list(2, 2, 3, 4))
     << endl;
     
cout << "Expanding an infinite list incrementally: ";
Polylist Ints = Polylist::from(0);
cout << Ints << endl;
Ints.prefix(10);
cout << "After expanding: " << Ints << endl;

cout << "Appending an infinite list to a finite one: " << endl
     << range(-10, -1).append(Ints) << endl;

cout << "Mapping conversion functions: " << endl;
Polylist Floats = range(0.5, 9.5);
cout << Floats.map(makeInteger) << endl;
cout << Floats.map(precat("x"))<< endl;

cout << "Some random integers in the range 0 to 99: " << endl;
Polylist random = Polylist::random(0, 100);
cout << random.prefix(20) << endl;

cout << "Modifying list structure: " << endl;
Polylist X = list(1, 2, 3);
X.rest().rest().rawRest() = list(4, 5, 6);
cout << X << endl;

cout << "Adding number to string, similar to Java" << endl;
cout << Poly("foo") + Poly(1234) + Poly("bar") << endl;

cout << "Appending one array to another (non-destructively): ";
A = range(0, 5);
Polyarray B = range(6, 9);
cout << A.append(B) << endl;

cout << "Appending arrays using ^ operator: ";
cout << (A ^ B ^ A) << endl;

cout << "Analyzing file input using deepType(): " << endl;
Poly p;
ifstream input2("test.in");
while( input2 >> p )
  {
  cout << "entered:  " << p << endl;
  cout << "deepType: " << p.deepType() << endl;
  }
cout << endl;

cout << "Using mappend: " << range(0, 5).mappend(upto) << endl;

cout << "mappend on infinite sequence: " 
     << from(0).mappend(upto).prefix(20) << endl;

cout << Polylist(append(from(0), from(0))).prefix(20) << endl;

cout << Polylist(range(0, 5).map(upto).foldl(append, nil)).prefix(20) << endl;

cout << Polylist(range(0, 5).map(upto)).prefix(20) << endl;

Oclosure Oc1(new occ1("hello,"));

cout << "Testing Oclosure: " << Oc1("world") << endl;

Oclosure Oc2 = Oc1;

cout << "Testing Oclosure: " << Oc2("goodbye") << endl;

cout << "Testing sequence of functions: ";
 
Polylist fns = map(precat, range(1, 5));

cout << map(applyto(", "), fns) << endl;


cout << "Testing sequence of oclosures: ";
 
Polylist ocs = map(addTag, range(1, 5));

cout << map(applyto("x"), ocs) << endl;
}


// Various functions used in examples

// squaring function

Poly square(Poly x)
  {
  floating z = x;
  return z*z;
  }


// Function2 for use with closure in scale

Poly scaler(Poly k, Poly x)
  {
  return k*x;
  }

// scaling function (higher-order)
  
Poly scale(Poly k)
  {
  return Fclosure(scaler, k);
  }


// odd-testing predicate

Poly isOdd(Poly x)
  {
  return integer(x) % 2 == 1;
  }


// floating-testing predicate

Poly isFLOATING(Poly x)
  {
  return isFloating(x);
  }


// predicate testing > 5

Poly greaterThan5(Poly x)
  {
  return integer(x) > 5;
  }


// predicate making list of 2 elements
// (plain list of 2 args is too overloaded)

Poly list2(Poly a, Poly b)
  {
  return list(a, b);
  }


// precat returns a function which concatenates a given string argument
// to its argument.

// precatFun is the closure function used by precat

Poly precatFun(Poly x, Poly y)
  {
  return cat(x, y);
  }

Poly precat(Poly x)
  {
  return Fclosure(precatFun, x);
  }


// upto(n) makes a list (0 .... n)

Poly upto(Poly n)
  {
  return range(0, n);
  }


Poly applier(Poly a, Poly f)
  {
  return f.operator()(a);
  }

// applyto(a) returns a function which, when applied to a function f,
// returns f(a).

Poly applyto(Poly a)
  {
  return Fclosure(applier, a);
  }

// addTag returns a function which adds a tag a to its argument

Poly addTag(Poly a)
  {
  return Oclosure(new occ1(a));
  }

main()
{
examples();
Poly::report();
}

