Functional programming is primarily about composing functions to transform a value.
So I might write the "functional-style" version as:
;(function(){// functional.js/*
Functional Core
*/// Result module// Either {ok} - contains the successful value// Or {err} - contains the error messageconstmakeOk=value=>({ok:value});constmakeErr=message=>({err:message});// Higher order functions (HOFs) for Result valuesconstandThen=fn=>rs=>{const{ok}=rs;if(ok===undefined)returnrs;returnfn(ok);};constmap=fn=>rs=>{const{ok}=rs;if(ok===undefined)returnrs;returnmakeOk(fn(ok));};// ValidationsconstvalidateRequired=message=>value=>value.trim().length>0?makeOk(value):makeErr(message);constdigits=newRegExp('\\s*\\d+\\s*');constvalidateInteger=message=>value=>digits.test(value)?makeOk(value):makeErr(message);constvalidateBetween=(message,lower,upper)=>value=>lower<=value&&value<=upper?makeOk(value):makeErr(message);// JavaScript isn't functional so iteration (+ mutation)// is more natural than recursion.functionfactorial(n){lettemp=[1,n];// iterate until terminating conditionwhile(temp[1]>1){temp=stepFactorial(temp);}returntemp[0];}conststepFactorial=([acc,n])=>[acc*n,--n];// Partially apply the functions and// "compose" the resulting single argument functions// to transform the input valueconstcomposition=[validateRequired('Please provide a value'),andThen(validateInteger('Please provide a integer')),map(n=>Number.parseInt(n,10)),andThen(validateBetween('Please specify an integer between 0 and 18',0,18)),map(factorial)];functionpipe(fns,value){letresult=value;for(leti=0;i<fns.length;++i){result=fns[i](result);}returnresult;}// transform the string to an {ok/err} resultconsttransform=value=>pipe(composition,value);/*
Imperative Shell
*/functionwhenDomUpdated(fn){requestAnimationFrame(()=>requestAnimationFrame(fn));}functioncalculateListener(event){constinputEl=event.target.querySelector('input');const{ok,err}=transform(inputEl.value);if(err===undefined){resultEl.innerText=ok.toString();}else{resultEl.innerText='';whenDomUpdated(()=>{alert(err);});}event.preventDefault();}// Initialization ScriptconstformEl=document.querySelector('#factorial-form');constresultEl=document.querySelector('#factorial-result');formEl.addEventListener('submit',calculateListener);}());
Edit: It's useful to remember Master Qc Na's lessons:
Objects are merely a poor man's closures
Closures are a poor man's object
Using a closure
functionvalidateBetween(message,lower,upper){returnfunction(value){// As part of its closure the returned function has access// to the `message`, `lower`, `upper` argument valuesreturnlower<=value&&value<=upper?makeOk(value):makeErr(message);};}constvalidate=validateBetween('Please specify an integer between 0 and 18',0,18);console.log(validate(18));// {ok: 18}console.log(validate(19));// {err: 'Please specify an integer between 0 and 18'}
versus using an object
classValidateBetween{constructor(message,lower,upper){// store argument values in object propertiesthis.message=message;this.lower=lower;this.upper=upper;}execute(value){// Stored `message`, `lower`, `upper` properties// are accessed via `this`returnthis.lower<=value&&value<=this.upper?makeOk(value):makeErr(this.message);};}constvalidate=newValidateBetween('Please specify an integer between 0 and 18',0,18);console.log(validate.execute(18));// {ok: 18}console.log(validate.execute(19));// {err: 'Please specify an integer between 0 and 18'}
composition and pipe could be replaced with:
functioncomposeFns(){constfn0=validateRequired('Please provide a value');constfn1=andThen(validateInteger('Please provide a integer'));constfn2=map(n=>Number.parseInt(n,10));constfn3=andThen(validateBetween('Please specify an integer between 0 and 18',0,18));constfn4=map(factorial);returnvalue=>fn4(fn3(fn2(fn1(fn0(value)))));}// transform the string to an {ok/err} resultconsttransform=composeFns();
or
// transform the string to an {ok/err} result// use an IIFE to initialize function closureconsttransform=(()=>{constfn0=validateRequired('Please provide a value');constfn1=andThen(validateInteger('Please provide a integer'));constfn2=map(n=>Number.parseInt(n,10));constfn3=andThen(validateBetween('Please specify an integer between 0 and 18',0,18));constfn4=map(factorial);returnvalue=>fn4(fn3(fn2(fn1(fn0(value)))));})();
Functional programming is primarily about composing functions to transform a value.
So I might write the "functional-style" version as:
Edit: It's useful to remember Master Qc Na's lessons:
Using a closure
versus using an object
composition
andpipe
could be replaced with:or
IIFE (Immediately Invoked Function Expression)