Love it or hate it, JavaScript is a reality most industry developers have to work with either as their primary programming language or on the periphery.
To help bridge the gap for a wide audience at various points in their functional programming journey, I will introduce a wide variety of ways to employ functional programming practices that produce JavaScript artifacts. From lodash, which offers common higher-order functions (HOFs) on collection structures and basic control flow all the way, to writing total dependently typed and/or pure functional code that produces JavaScript from different source languages with varying guarantees.
Staying “close to the metal”*
Maybe you have to remain writing your source code in JavaScript for some reason (although starting to add TypeScript to an existing JavaScript codebase is a simple one-time setup).
In this case, there are a number of JavaScript libraries that offer developers the ability to start using higher-order function primitives found in all functional languages I’m familiar with.
Lodash (forked from underscore.js) is one popular library that offers utility functions for working with arrays, strings, numbers, objects, and building functional pipelines via more compositional control flow.
It contains many of the functions you would expect for Arrays that you might be familiar with in Haskell’s Data.List
module:
const fruits = [ 'apples', 'bananas', 'cantelopes', 'durians', false];
const chunked = _.chunk(fruits, 2);
const compacted = _.compact(fruits);
const filled = _.fill(fruits, 'bananas');
const flattened = _.flatten(chunked);
const takeThree = _.take(fruits, 3);
Yet it also offers a precursor to lenses and traversals with new syntax like so:
const object0 = { 'foo': [{ 'bar': { 'baz': 3 } }] };
_.get(object0, 'foo[0].bar.baz');
const object1 = {'foo': [{ 'bar': { 'qux': 15, 'quux': true } }] };
const object2 = _.merge(object0, object1);
const bar = _.get(object2, 'foo[0].bar');
_.pick(bar, ['qux', 'baz']);
The documentation for lodash has great examples to illustrate usage.
Other libraries that offer functional interfaces for similar utilities or parsing or persistent data structures include:
Ramda: A practical functional library for JavaScript that is designed specifically for a functional programming style.
Masala Parser: Javascript parser combinators
Immutable.js: Immutable.js provides many Persistent Immutable data structures including:
List
,Stack
,Map
,OrderedMap
,Set
,OrderedSet
andRecord
.
* The quotes in this section’s headline were inspired by this reddit comment.
Leveraging functional abstractions
Another related library for JavaScript/TypeScript applications to use is fp-ts which (in its own words) “provides developers with popular patterns and reliable abstractions from typed functional languages in TypeScript.”
This offers the application developer the power of Functors, Foldables, Traversables, Applicatives, Alternatives, and more!
You might be wondering what this looks like so here is a quick illustration of Option and Applicative’s sequence adjusted for TypeScript’s ergonomics:
import { Option, some, none, option } from 'fp-ts/lib/Option';
import { sequenceS } from 'fp-ts/lib/Apply';
interface Money { currency: Currency; amount: number; };
type PricingInfo = { effectiveDate: Date; price: Money; ticker: string; };
enum Currency { USD = "USD", EUR = "EUR", JPY = "JPY", CHF = "CHF", GBP = "GBP", CAD = "CAD", AUD = "AUD", };
const parseRowOption = (row: string[]): Option<PricingInfo> => {
if (row.length >= 3) {
const [dateStr, moneyStr, ticker, rest] = row;
const date = parseDateOption(dateStr);
const money = parseMoneyOption(moneyStr);
const ticker = parseTickerOption(ticker);
return sequenceS(option)({ effectiveDate: date, price: money, ticker });
} else {
return none;
}
}
// Assumes we have (implement them as an exercise):
// parseDateOption = (s: string) : Option<Date>
// parseMoneyOption = (s: string) : Option<Money>
// parseTickerOption = (s: string) : Option<string>
The above shows we have broken out the logic into smaller parts and sequenceS in fp-ts allows us to use the option
Apply instance to only construct a PricingInfo
value if all the fields were parsed successfully without the error-prone imperative constructions we are used to seeing in JavaScript.
Compared to Haskell or PureScript this might seem verbose but is a good option to leverage these powerful functional abstractions in TypeScript if that is what your project is written in.
Update: @anthonyjoeseph on GitHub heckled the above in a Gist and it is a lot better for two reasons:
As mentioned in this tweet, it also introduces two important functions made available from fp-ts,
pipe
andchain
plus a (safe) utility function for Arrays (also from fp-ts) I didn’t about before,lookup
.This Gist is expression-oriented programming whereas the code above is a transitional style introducing functional programming concepts to those not yet familiar with expressing logic entirely with values (take from a work session live coding with coworkers iterating a very imperative program to gradually more functional). You might ask, what is the alternative to expressions? That would be statements. The above uses if/else statements. The problem with statements is that you must rely on side effects.
Our next part of the twitter dialog discusses the introduction to the pipeline operator in ES2019 which will make the Gist offered even easier to scan (IMO). TODO: Add new Gist showing what it could look like.
Using a new source language
We saw above that JavaScript and TypeScript aren’t ergonomic to use with functional abstractions but we can get busy with simple higher-order functions pretty seamlessly. What if you wanted to take your functional programming practice to the next level and use languages that offer:
custom infix operators (to improve ergonomics in some cases)
direct encodings for algebraic data types
expressive types and kinds
builtin support for typeclasses
a module system
effect systems
Many languages provide one or more of the above which also transpile down to JavaScript including (but not limited to):
Elm is an entry-level functional source language and one of the more popular JavaScript alternatives after TypeScript that requires some investment in its ecosystem than the other languages without as much of an expressive type system as most of the rest and the first (that I know of) to offer user-friendly compiler messages though this is fast becoming the norm.
ReasonML offers a variation of OCaml to transpile to JavaScript and good interop. ReScript combines Reason with BuckleScript (OCaml-based JavaScript compiler).
PureScript provides a Haskell-like syntax without the laziness by default and many of Haskell’s most useful type-level capabilities (but not all extensions). It has good foreign function interfacing (FFI) for interop with JavaScript and is growing the number of backends available for it including compiling to Go, Erlang, Python, and Kotlin amongst other targets. It has a wide variety of bindings for frontend libraries and bindings to popular JavaScript backend frameworks like Express plus its own. It is used in production by Lumi, Awake Security, CitizenNet, JusPay for building frontend web applications plus others that are building serverless applications on the backend. Personally, this is my favorite language to generate JavaScript (and Go too).
Haskell/GHCJS offers Haskellers a way to generate JavaScript from their Haskell code. It ships with a small runtime in JS to support its evaluation strategy which differs from JavaScript’s. There are several maturing toolkits that provide the ability to build interactive web UIs and build mobile app artifacts such as Reflex. This is my second favorite language for building JavaScript applications, however, is sometimes impractical.
ScalaJS gives the Scala world a comfortable way to build JavaScript-based applications. A whole host of Scala libraries now support Scala.js and the compatible libraries will likely continue to grow as the Scala community embraces multiple targets including Scala.js and Scala Native.
Fable is based on F# and if you are already in the F# or .NET ecosystem, this might be worth investigating.
Idris2 announced last week that its newest version now has a
node
and ajavascript
backend. This is not going to be a productive experience for building a web application (yet), but if you need to use dependent types then this could be for you!
Updates:
I was informed that I missed js_of_ocaml:
And that ReScript has a slightly different syntax to Reason:
Freedom from the tyranny of types (if you want that)
Those that just want the functional guts without the type system support that the above languages provide to varying degrees have these options:
ClojureScript: for those wishing to exploit the Clojure ecosystem to target JavaScript you have ClojureScript, which is not mainstream yet is a mature development environment to get started building non-trivial applications with good documentation.
WhaleSong: for die-hard Racket fans or those who learned to program using Racket tools, WhaleSong is a fun project to get started programming for the web.
Compiling to JSON (and YAML)
You might be perplexed by this heading, however, if you need to maintain large amounts of configuration (in JSON or YAML), it quickly becomes clear that being able to generate configuration formats that interoperate with JavaScript applications (especially via JSON) is an essential activity for many systems today. It becomes tedious to build large static JSON by hand. Wouldn’t it be great if we had a functional language that could generate configuration into different formats including JSON? Problem solved; Dhall is here. I have used this for managing application and system configuration at work previously and would use again in a heart beat. We benefited from its type checking (ensuring all environments had all required settings of the expected type) and from the ability to define functions that generate our configuration.
The above was a whirlwind tour of functional programming targeting either browser or server-side JavaScript (or both).
Heckle me on Twitter to let me know what I missed and I might even update this post! Cheers. :)