AK23
AK23

Reputation: 43

functional composition javascript

I'm trying to understand functional composition in Javascript and followed a talk which describes the compose function

I tried the following program which will accept a sentence, break it up into pieces (delimited by space) and use uppercase on each word and return an array of words.

function compose(f, g) {
    "use strict";
    return function() {
        return f.call(this, g.apply(this,arguments));
    }
}

var split = function (string, delim) {
    "use strict";
    return string.split(delim);
};

var uppercase = function (string) {
    "use strict";
    if (string instanceof Array) {
        return string.map(function (x) {
            return x.toString().toUpperCase();
        });
    } else {
        return string.toString().toUpperCase();
    }

    //return string.toString().toUpperCase();
};

var myfunc = compose(uppercase,split);

var data = myfunc("Elementary! My dear Watson",/\s+/);
console.log(data);

Though I got what I wanted, but the code is ugly on following counts:

  1. I've to re-define split and toUpperCase as I constantly got "Reference error: toUpperCase not defined". Is it because they are methods and not pure functions per se?
  2. the uppercase method is ugly because it should receive just a single string so that it flips the case,but since I'm tokenizing it, this function receives an array and hence the ugly array check.

How can the code be improvised to have a "pipe of functions" viz. split -> map -> uppercase ?

Upvotes: 1

Views: 1541

Answers (4)

Ananthaprakash
Ananthaprakash

Reputation: 131

composing function allows reusability of functions. You can apply javascript reduce method to apply set of functions on data.

Lets start with a basic reduce sample. The following is set of functions to calculate sale price.

const SALES_TAX = 0.08;
const COUPON_CODES = {
  AAAA: 0.1,
  BBBB: 0.07,
  CCCC: 0.15,
};

const shoppingtotal = shoppingCart =>
  shoppingCart.reduce((acc, item) => {
    acc += item.price * item.qty;
    return acc;
  }, 0);
const discount = couponCode => amount =>
  amount * (1 - COUPON_CODES[couponCode]);
const tax = amt => amt * (1 + SALES_TAX);

I can create a compose as below

const compose = fns => input => fns.reduce((acc, fn) => fn(acc), input);
const calculatePayment = compose([shoppingtotal, discount('AAAA'), tax]);

Now to calculate price, I can call calculatePayment.

calculatePayment([
{
  name: 'pencil',
  price: 1,
  qty: 2,
},
{
  name: 'Pen',
  price: 1.5,
  qty: 20,
},
{
  name: 'Eraser',
  price: 0.25,
  qty: 10,
}])

It is a sample. Like this, you can easily compose multiple functions and create new function definitions.

Upvotes: 0

JoeCortopassi
JoeCortopassi

Reputation: 5093

Functional composition is just when you take two or more functions, and make one a single function out of them. I've written up a pretty easy to follow explanation of composing functions that I think will clear up quite a few things for you.

What you want however is something very different, and is very straight forward in javascript

"Elementary! My dear Watson".split(/\s+/).map(val => val.toUpperCase());

or if you really want it as a function like you have above...

function myFunct(str, delim) {
    str.split(delim).map(val => val.toUpperCase());
}

Upvotes: 0

synthet1c
synthet1c

Reputation: 6282

Here is the same sort of code that you have which has been simplified a little. If this is the direction you want to take with your coding I cant recommend Javascript Allonge by Reg Braithwait enough, It completely changed the way I thought about writing code.

Hopefully this small example shows how to compose functions and how useful functional js is. Another big benefit is that if you are using pure functions that don't look to the outer scope you can easily debug issues as there is a dependable stream of function calls that you can follow.

Learning Functional enough to be able to understand it's basic concepts took me about 6 months and constant practice. but it was well worth the effort.

"use strict"; // you just need one use strict on the hholee file
// for compose you shouldn't really need to call or apply as this is always the window for a pure function
function compose(a, b) {
    return function( c ) {
        return a( b( c ) );
    }
}

// split is now a simple function that takes a delimeter and returns a function that splits the string
function split(delim) {
  return function( string ){
     return string.split(delim);
  }
};

// if you add a function mapper you can remove some of the complexity in uppercase
// and compose map with uppercase
function map( fn ){
  return function( arr ){
    return Array.prototype.map.call( arr, fn );
  }
}

// this is now a very simple single responsibility function that can be composed
function uppercase(string) {
    return string.toUpperCase();
};

var 
  splitBySpace   = split(/\s+/),
  arrToUpper     = map(uppercase),
  myfunc         = compose(arrToUpper,splitBySpace);

console.log( arrToUpper(['one', 'two']) );

console.log( myfunc("Elementary! My dear Watson") );

// if you want to split on a different character you will need to create a new split function by currying a different delimeter
var 
  splitByChar    = split(''),
  splitByBang    = split('!');

// you also don't have to compose the functions you can write them out normally but it is much harder to follow eg.
console.log( map(uppercase)(split('')("Elementary! My dear Watson") ) );
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>

Upvotes: 2

synthet1c
synthet1c

Reputation: 6282

Here is a small example of how to get a fluent interface up and running covering the third part of your question. the key thing to take away is you create an object with new Obj and in the methods you need to return this to allow method chaining.

Functional Javascript cannot allow chaining methods as it has no internal state, you use functional js to decorate and mix other functions that can return either a value or another function.

// MyObj is declared within a module to make the internals private
var myObj = (function() {

  // Constructor function
  function MyObj(string, delim) {
    this.input = string;
    this.delim = delim;
  }

  // create the MyObj methods on it's prototype
  // all methods have been decorated with the fluent function
  MyObj.prototype = {
    // split the input
    split: fluent(function(delim) {
      if( ! Array.isArray( this.input ) ){
         this.input = this.input.split( delim != null ? delim : this.delim);
      }
    }),
    // convert the text to uppercase
    uppercase: fluent(function() {
      if (Array.isArray(this.input)) {
        this.input = this.input.map(function(string) {
          return string.toUpperCase();
        });
      } else {
        this.input = this.input.toUpperCase();
      }
    }),
    // reverse the array
    reverse: fluent(function(){
      if( Array.isArray( this.input ) ){
        this.input = this.input.reverse();
      }
      else {
        this.input = this.input.split('').reverse().join('');  
      }
    }),
    // you will need a getter if you are using a fluent interface or decide what your end points will be
    get: function() {
      return this.input;
    }
  }

  return function constructor(string, delim) {
    return new MyObj(string, delim);
  }

})();

// myObj is the function to create a MyObj
console.log( myObj );

console.log( myObj("Elementary! My dear Watson", /\s+/ ).split().uppercase().get() );

// with the code you can override the default delimeter
console.log( myObj("Elementary! My dear Watson", /\s+/ ).split('').uppercase().get() );

// run different methods
console.log( myObj("Elementary! My dear Watson", /\s+/ ).split().uppercase().reverse().get() );

// order no longer matters
console.log( myObj("Elementary! My dear Watson", /\s+/ ).reverse().uppercase().split().get() );

// MyObj also has an internal state so you can do

// functional decorator - this will make the prototype methods return a value or this
function fluent(method) {
  return function() {
    var ret = method.apply(this, arguments);
    return ret != null 
      ? ret 
      : this;
  }
}
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>

Upvotes: 0

Related Questions