Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

webit
webit

Posted on

     

Composing functions in JavaScript

Recently I read an article about usingPipeline style in JavaScript.
An article described how to pipe functions together so data flows through all of them.
What I've missed in this article was functional programming taste.
Let's go a step further and add some FP flavor.

Using pipelines in *nix shell

Imagine *nix command line where we want to find allindex.js files in a certain directory. When we will get a list of files we would like to count them.
Let's say we got source code placed insidesrc/.
It's a trivial example but explains how we can use pipe commands (using|) in *nix shell to pass data through them.

To achieve what we want we have to execute the following command:

tree src/ | grep index.js | wc -l
Enter fullscreen modeExit fullscreen mode

Where:

  • tree recursively lists directories (in the example I limit it tosrc/ directory)
  • grep is used to filter results (single line) with provided pattern - we want only lines that containindex.js
  • wc (word count) returns newline count, word count, and byte count. Used with-l returns only the first value so the number of times ourindex.js was found

Example output from the above command can be any number, in my case, it's26.

What we see here is how data is passed from one command to another. The first command works on input data and returns data to the second one. And so on until we reach the end - then data returned by the last command is displayed.

Using pipelines in JavaScript

We can achieve a similar thing in JavaScript.
First, let's build a function that serves for certain purpose mimicking shell commands.

// node's execSync allows us to execute shell commandconst{execSync}=require("child_process");// readFiles = String => BufferconstreadFiles=(path="")=>execSync(`tree${path}`);// bufferToString = Buffer => StringconstbufferToString=buffer=>buffer.toString();// makeFilesList = String => ArrayconstmakeFilesList=files=>files.split("\n");// isIndex = String => BooleanconstisIndexFile=file=>file.indexOf("index.js")>0;// findIndexFiles = Array => ArrayconstfindIndexFiles=files=>files.filter(isIndexFile);// countIndexFiles = Array => NumberconstcountIndexFiles=files=>files.length;
Enter fullscreen modeExit fullscreen mode

Let's see what we got so far:

  • readFiles() function executestree command for providedpath or in location where our JS file was executed. Function returns Buffer
  • bufferToString() function converts Buffer data to String
  • makeFilesList() function converts received string to array making each line of text separate array element
  • isIndexFile() function check if provided text containsindex.js
  • findIndexFiles() function filters array and returns new array with only entries containingindex.js (internally usesisIndexFile() function)
  • countIndexFiles() function simply counts elements in provided array

Now we got all the pieces to do our JavaScript implementation. But how to do that?
We will usefunction composition and the key here is usingunary functions.

Function composition

Unary functions are functions that receiveexactly one parameter.

Since they accept one argument we can connect them creating a new function. This technique is calledfunction composition. Then data returned by one function is used as an input for another one.

We can usecompose function that you can find in the popular functional programming libraryRamda.
Let's see how to do that...

// returns function that accepts path parameter passed to readFiles()constcountIndexFiles=R.compose(countIndexFiles,findIndexFiles,makeFilesList,bufferToString,readFiles);constcountIndexes=countIndexFiles("src/");console.log(`Number of index.js files found:${countIndexes}`);
Enter fullscreen modeExit fullscreen mode

Note: we can actually compose functions without even usingcompose function (but I think this is less readable):

constcountIndexes=countIndexFiles(findIndexFiles(makeFilesList(bufferToString(readFiles("src/")))));console.log(`Number of index.js files found:${countIndexes}`);
Enter fullscreen modeExit fullscreen mode

As you can see function composition allows us to join functions and don't worry about handling data between them. Here's what we have to do without using composition:

constfilesBuf=readFiles("src/");constfilesStr=bufferToString(filesBuf);constfilesList=makeFilesList(filesStr);constindexFiles=findIndexFiles(filesList);constcountIndexes=countIndexFiles(indexFiles);
Enter fullscreen modeExit fullscreen mode

Compose vs pipe

As you might have noticed when usingcompose we need to pass functions in the opposite order they are used (bottom-to-top).
It's easier to read them in top-to-bottom order. That's the place wherepipe comes in. It does the samecompose does but accepts functions in reverse order.

// even though not takes functions list in reverse order// it still accepts path parameter passed to readFiles()constcountIndexFiles=R.pipe(readFiles,bufferToString,makeFilesList,findIndexFiles,countIndexFiles);constcountIndexes=countIndexFiles("src/");console.log(`Number of index.js files found:${countIndexes}`);// same result as before
Enter fullscreen modeExit fullscreen mode

It depends just on us which one method we will use -compose orpipe.
Try to use one you (and your colleagues) feel better with.

Bonus: use full power Ramda gives you

We can use other Ramda methods to even more shorten our code. This is because all Ramda functions arecurried by default and come with the "data last" style.
This means we can configure them before providing data. For exampleR.split creates new function that splits text by provided separator. But it waits for a text to be passed:

constipAddress="127.0.0.1";constipAddressParts=R.split(".");// -> function accepting stringconsole.log(ipAddressParts(ipAddress));// -> [ '127', '0', '0', '1' ]
Enter fullscreen modeExit fullscreen mode

Enough theory 👨‍🎓
Let's see how our code could look like in final (more FP style) form:

const{execSync}=require("child_process");constR=require("ramda");// readFiles = String => BufferconstreadFiles=(path="")=>execSync(`tree${path}`);// bufferToString = Buffer => StringconstbufferToString=buffer=>buffer.toString();// isIndex = String => BooleanconstisIndexFile=file=>file.indexOf("index.js")>0;constcountIndexFiles=R.pipe(readFiles,bufferToString,R.split("\n"),R.filter(isIndexFile),R.length);constcountIndexes=countIndexFiles("src/");console.log(`Number of index.js files found:${countIndexes}`);
Enter fullscreen modeExit fullscreen mode

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Web developer 💻 Cycling addicted 🚴🏼‍♂️ Making web since last century 🦖
  • Location
    Poland
  • Work
    Senior Frontend Developer
  • Joined

More fromwebit

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp