/* Summation Function with Functional Parameters
   Functional Programming Example - Higher Order
   H. Conrad Cunningham, Professor
   Computer and Information Science
   University of Mississippi

   Developed for CSci 555, Functional Programming, Spring 2016

1234567890123456789012345678901234567890123456789012345678901234567890

2016-02-10: Developed from 2015 Elixir version

I adapted this summation module from an Elixir version, which as, in
turn, adapted from a Lua version, which was, in turn, adapted from the
Scheme code in section 1.2.1 of Abelson and Sussman's Structure and
Interpretation of Computer Programs (SICP).

I have only tested this minimally.

Scala and functional programming highlights:
- Illustrates generalization of first-order functions into
  higher-order
- Explicitly defined return type as Double
- Uses anonymous functions
- Defines val to have function value

*/

object Summation {

  /* Function "sumIntegers" computes the sum of the integers in the
     range from "a" through "b".
  */

  def sumIntegers(a: Int, b: Int): Int =
    if (a > b)
      0
    else
      a + sumIntegers(a+1,b)

  /* Function "sumCubes" computes the sum of the cubes of the
     integers in the range from "a" through "b".
  */

  def sumCubes(a: Int, b: Int): Double = {
    val cube = ((x: Int) => x * x * x)
    if (a > b)
      0
    else
      cube(a) + sumCubes(a+1,b)
  }
  
  /* Function "piSum" computes the sum of the series of terms
     1/(i*(i+2)) from i = a until i > b.
   
     For a = 1 and b = 10 this would be 1/1*3 + 1/5*7 + 1/9*11.
     For initial a = 1, this converges slowly on PI/8.
  */

  def piSum(a: Int, b: Int): Double =
    if (a > b)
      0.0
    else 
      1.0 / (a * (a+2)) + piSum(a+4,b) 


  /* The above all satisfy the following template of the following
     form for some NAME, TERM, and NEXT.

       def NAME(a: Int, b: Int): Double =
         if (a < b) 0 else TERM(a) + NAME(NEXT(a),b)

    We can express this function as a higher-order function (for NAME)
    with functional arguments for the operations TERM and NEXT.

    Function "sum" adds the values of the application of function
    "term" to integer i for all integers i in the range [1,b], where
    the difference from i to its successor j is next(i).

    Note: I explicitly defined the return type to be Double to avoid
    complicating the function with generics and extra parameters.

  */
  
  def sum(term: Int => Double, a: Int, next: Int => Int, b: Int): Double=
    if (a > b)
      0.0
    else
      term(a) + sum(term,next(a),next,b)
      
  // sumIntegers in terms of sum 
  def sumIntegers2(a: Int, b: Int) =
    sum(((x:Int) => x), a: Int, ((x:Int) => x+1), b:Int)

  // sumCubes in terms of sum
  def sumCubes2(a: Int, b: Int): Double = {
    def cube(x: Int): Double = x * x * x
    def incr(x: Int): Int    = x+1
    sum(cube,a,incr,b)
  }

  // piSum in terms of sum
  def piSum2(a: Int, b: Int): Double = {
    val piTerm = ((x: Int) => 1.0 / (x * (x+2)))
    def piNext(x: Int): Int    = x + 4
    sum(piTerm,a,piNext,b)
  }

  /* Function "sum" can be further generalized in various ways.

     Question: How would we need to modify "sum" so that the resulting
     higher-order function is capable of either summing or multiplying
     the integers over a range?
  */

  // Should implement extensive tesing externally
  def main(args: Array[String]) {
    println("sumIntegers(1,10)  = " + sumIntegers(1,10))
    println("sumIntegers2(1,10) = " + sumIntegers2(1,10))
    println("sumCubes(1,5)      = " + sumCubes(1,5))
    println("sumCubes2(1,5)     = " + sumCubes2(1,5))
    println("piSum(1,10)        = " + piSum(1,10))
    println("piSum2(1,10)       = " + piSum2(1,10))
  }

}
