Tuesday, July 1, 2014

ClassCloud.applyP.js

/***********************************************************

Implements the JavaScript -function 'ClassCloud.applyP()' 
which creates "Partially Applied" versions of any function, 
to "fix" part of their arguments. See the tests -section
below for how to use applyP(). 
URL: http://panuviljamaablog.blogspot.com/2014/07/classcloudapplypjs.html 
*********************************************************** 
   
This software is made available under
"The MIT License", http://mit-license.org/.

Copyright © 2014 Panu Viljamaa, ClassCloud LLC

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


function ClassCloud  ()
{ /* This is the only global created by the loading 
     of this js-file. After loading this file you can 
     get a reference to the applyP() -function like this:
     var applyP = ClassCloud.applyP;
   */
}

(function anonymous  ()
{ "use strict" ;
  var _PREVIOUSLY_GIVEN_ARGS;
  var _MISSING_ARGS_L       ;
  ClassCloud . applyP = applyP;
  return;


  function applyP (aFunction, a,b,c,d,e)
  {
    _PREVIOUSLY_GIVEN_ARGS  = [];
    var argsArray = [].slice.call(arguments);
    var fargs       = argsArray.slice(1);
    var neededArgsL = numbOfArgs(aFunction);
   
    if (fargs.length > neededArgsL)
    { var emsg =
        "Too many args given to ClassCloud.applyP() "
        + fargs.length  + ". "
        + "The base-function only expects " 
        + neededArgsL   + ": \r\n"
        + aFunction;
        throw emsg;
    }
   
    var result      
     = applyPBasic (aFunction, fargs, neededArgsL);
    return result;
    
      /**
       Return the 1st argument partially applied
       to the rest of the arguments.  
       */
  }

   function numbOfArgs(aFunction)
   { var s  = aFunction.toString();
     var p1 = s .split(/\)/) [0];
     var p2 = p1.split(/\(/) [1];
     if (p2.match(/^\s*$/) )
     { return 0;  // IE7 fix
     }
     var argNames = p2.split(/,/);
     return argNames.length;
   }


  function applyPBasic (topFunk, fargsArray, missingArgsLength  )
  {/*  
      'missingArgsLength' does NOT yet include the ones that
      may now be given in fargsArray
    */
    var knownArgs           = _PREVIOUSLY_GIVEN_ARGS;
    var argsKnownNow        =  knownArgs.concat(fargsArray);
    _PREVIOUSLY_GIVEN_ARGS  = argsKnownNow;
    _MISSING_ARGS_L         
       = missingArgsLength - fargsArray.length;
   
    var mislen = _MISSING_ARGS_L
    if (mislen == 0)
    { var result = topFunk.apply(this, argsKnownNow);
      return result;
    }
    return newFunk;

    function newFunk (y,z)
    { var result;
      var myArgsArray = [].slice.call (arguments);
      var previouslyGivenArgs = _PREVIOUSLY_GIVEN_ARGS;
      var argsKnownNow 
        = previouslyGivenArgs.concat(myArgsArray);
       
      var weKnowAllArgs
        = (_MISSING_ARGS_L - myArgsArray.length) <= 0;

      if ( weKnowAllArgs )
      { result = topFunk.apply(this, argsKnownNow );
        return result;
      }
      var nextLevelF
         = applyPBasic (topFunk, myArgsArray, _MISSING_ARGS_L);  
      return nextLevelF;
    }
  }
}) ();



// ====================  TESTS: ======================


(function anonymous2 ()
{ "use strict" ;
  if (false)   
  { /*  Change false above to true, to skip
        running these tests, which makes sense
        if you include this in the production
        version of your software. You could
        of course remove these tests alltogether.
        You can modify this source-code except
        as restricted by the MIT license given
        at the top.
    */
   return
  }

  var applyP = ClassCloud.applyP;

  testF2();  // Simple but interesting
  testF3();  // N arguments.
  testF1();  // Trivial case
  testF0();  // Pathological case

 return;
  
  
  function testF2 ()
  { 
     ok (applyP (f2, 1, 2)          == 3);
     ok (applyP (f2, 1   ) (2)      == 3);
     ok (applyP (f2      ) (1) (2)  == 3);
     ok (applyP (f2      ) (1,2)    == 3);
     /* 
        f2() sums up its TWO arguments.
        The last 2 cases above are interesting because 
        they show that we can do partial application 
        without passing in ANY arguments. See the 
        blogpost for more explanation. 
        */
     function f2 (x,y){ return x + y}
  }

  function testF3 ()
  {
     ok(applyP (f3      ) (1)(2)(3) == 6); // a)
     ok(applyP (f3      )   (1,2,3) == 6); // b)
     
     ok(applyP (f3, 1   )    (2)(3) == 6);
     ok(applyP (f3, 1   )     (2,3) == 6);
     
     ok(applyP (f3, 1, 2)       (3) == 6);
     ok(applyP (f3, 1, 2, 3)        == 6);  // c)
     /*
       Above shows that there are 3 ways you can  
       inject the arguments into the calculation: 
       a) You can give them ONE BY ONE to the result 
          of applyP().
       b) You can give one or more of them in a SINGLE
          call to the result of applyP().
       c) You  can give them ALL when calling applyP(). 
      */
     return;
      
     function f3 (x,y,z)   { return x + y + z }
  }
  
  
  function testF1 ()
  {
   var f1P =  ClassCloud.applyP (f1);
   f1P ()  ;  
   ok( f1P()()()().constructor == Function)  ;
   // As long as you call f1P with TOO FEW arguments
   // you will be getting a curried function back.
   
   ok (applyP (f1) (55) == 55)  ;

   var emsg = null;
   try 
   { applyP (f1, 1, 2)  ;
     /* Above fails because you are trying to
        apply more arguments than the base
        function has declared to itself. 
        Calling it with too many args would
        be of no use so we treat it as error.
    */
   } catch (e) 
     { emsg = e;
     }
   ok (emsg != null);  
   return; 

   function f1 (x)   { return x}     
  } // end testF1.


  function testF0 ()
  {  
   ok (ClassCloud.applyP (f0) == 123);
       
   var emsg = null;
   try 
   { ClassCloud.applyP (f0)()   ;
     /* Above fails because ClassCloud.applyP(f0)
        return 123, which is not a function.
      */
   } catch (e) 
     { emsg = e;
     }
   ok (emsg != null);  
   
   function f0 ( )   { return  123; }
     
  } // end testF0
  
  function ok (bool)
  { if (! bool)
    { throw "ERROR. ok(bool): bool == " + bool ;
    }
    /**
     Our simplest possible testing-utility
     */
  }
}) ();

No comments:

Post a Comment