maryamyriameliamurphies v0.9.4
maryamyriameliamurphies.js
Learn functional programming in ES2015 JavaScript from the principles and code patterns of Haskell
Make your own code more functional by using this library as it is or just implementing its ideas yourself
Now in version 1.0
- Comprehensive HTML documentation!
- Linting your mother would be proud of!
- Fully tested—with guaranteed 100% code coverage!
- Standalone browser bundles at no extra charge!
- Now ISC licensed!
- Not scary!
- Monads!
All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor. — Saunders Mac Lane, Categories for the Working Mathematician
In general, an arrow type will be a parameterised type with two parameters, supporting operations analogous to return and >>=. Just as we think of a monadic type m a as representing a 'computation delivering an a', so we think of an arrow type a b c (that is, the application of the parameterised type a to the two parameters b and c) as representing a 'computation with input of type b delivering a c'. — John Hughes, Generalising monads to arrows
In mathematics, a functor is a type of mapping between categories which is applied in category theory. Functors can be thought of as homomorphisms between categories. In the category of small categories, functors can be thought of more generally as morphisms. — Wikipedia, Functor
murphy come, murphy go, murphy plant, murphy grow, a maryamyriameliamurphies, in the lazily eye of his lapis — James Joyce, Finnegans Wake
About
maryamyriameliamurphies.js is a library of Haskell-style morphisms implemented in JavaScript using ECMAScript 2015 syntax. That is, it's a collection of pure functions designed to showcase in a more widely-used language than Haskell the ways and means of functional programming, for which the newest dialect of JavaScript has improved support. If you're interested in functional programming or even Haskell itself, but find that world intimidating, this library may be a useful conceptual bridge. The syntax I use will probably come across as unconventional, as it mirrors as closely as possible the terse, efficient style of Haskell. Eventually, however, you may find it easy enough to reason about, thanks to its relative lack of side effects and foundation in function composition. If you're curious about strange-sounding things like functors, monads, partial application, currying, and lazy evaluation, then there's something here for you.
First published entirely by chance on St. Patrick's Day, 2016.
Try it now with Tonic
Complete Online API Documentation
How to install
- Copy and paste the code. Go nuts.
git clonethis repo and then executenpm install && npm run compileto compile the code.- Install with npm
npm install --save-dev maryamyriameliamurphies.
How to use with npm if you clone
npm run compileto run Babel on ES2015 code in./sourceand output transpiled ES5 code to./distribution.npm run lintto run ESlint to check the source code for errors.npm testto run Mocha on the test code in./test.npm run coverto run nyc on the source code and generate testing coverage reports.npm run docto run JSDoc to generate HTML documentation for the entire library.npm run bundleto run Browserify to bundle the library for use in the browser.npm run cleanto delete all files in./distribution.npm run buildto runclean,compile,bundle, anddocall at once.
These commands require that you have certain npm packages installed. See below.
How to test in the browser
- Create a new HTML file in the
./bundledirectory, for examplemaryamyriameliamurphies.html. - Paste this code into it, after any other content:
<script src="maryamyriameliamurphies.js"></script>. - Open your browser's JavaScript console.
- Test functions using the library namespace, e.g.:
const m = require('maryamyriameliamurphies'); // yes, I know it's too long
const hello = str => { return m.print(`Hello ${str}!`), m.just(str); }
const str = m.just(`world`);
const sayHello = () => {
m.Do(str)
.flatMap(hello)
.inject(`monad`)
.flatMap(hello);
}
sayHello();
// "Hello world!"
// "Hello monad!"Your mileage may vary, depending on which browser you use to test. This example works in the latest version of Chrome, but ES2015 syntax is not fully supported in Safari as of this writing.
Description
Since the average explanation of functional programming terminology makes about as much sense to the average reader as the average page of Finnegans Wake, I gave this library a whimsical, literary name. Also, I'm an English literature Ph.D. student, and functional code strikes me as poetic (as "composed" in multiple senses) even though the technical explanations are impenetrably obtuse. All you need to know—in fact, all I understand—is that a pure function (or a morphism in general) simply describes how one thing can predictably transform into another. So a functional program, much like Joyce's final work, is an extended description of how things change.
These functions are experimental, as Haskell's type system translates only awkwardly to a JavaScript idiom, but I'd be delighted if any of them turn out to be useful. I tried hard to make them as pure as possible, which is why most (but not all) of them accept as arguments and return as values single values, and very few are defined as methods on prototypes. I also followed Haskell code patterns as closely as I could for each implementation (as much as it made sense to do so), resulting in a style that is sometimes extremely straightforward and sometimes bewilderingly terse.
There are two Haskell concepts that I use in the code that do not perfectly fit into the JavaScript way of doing things: the type class and the data type. In Haskell, a type class is similar to a protocol or trait in other programming languages. It describes an interface that objects conforming to it must implement.
A type class is a way of making fully parameterized types more useful by placing constraints on them. For example, the Eq type class in this library provides functionality for comparing whether the objects that implement it are equal. Objects that provide their own isEq() function will perform this test and return a boolean. Note that Haskell type classes are in no way comparable to "classes" in OOP.
A data type, on the other hand, is much closer to an OO class definition, as it does describe a custom type. The Tuple type is an example of a data type, as it represents a container for other, more basic values. As is often the case with objects in classical languages, instances of Haskell data types are created with special constructor functions that initialize them based on the arguments to those functions. A data type does not inherit (in the usual way) from other data types, however. Instead, it describes how constructor functions convert values passed in as arguments to those functions into the values that comprise that particular type.
As mentioned above, data types can be constrained (or not) by type classes, so as to provide additional functionality—Eq is an example of this, as is Ord, a type class that allows objects to be compared (greater than, less than, etc.). Tuple implements both of these type classes, as one may rightly want to compare tuples or test them for equality, for example.
Since JavaScript is not a strongly typed language by nature, it seemed unnecessary to me (and, for better or worse, antithetical to the JS spirit) to recreate the entirety of Haskell's static type system, though I do provide a limited amount of type checking. Anyone interested in better type safety should probably be using something like PureScript or GHCJS. Instead, I use the new ES2015 class pattern for data types with static methods defined on those classes to provide the functionality of type classes. Since the classes and their constructors are not exposed in the API this library provides, instances of data types must be created using specialized functions provided for this purpose. This keeps the static "type class" methods private and affords some degree of namespace protection for the data types.
ES2015 specifies tail call optimization, which will ensure that all the nifty Haskell-esque recursions this library uses won't blow up your call stack (when it's actually implemented).
See also
Development
Requires:
- Node - JavaScript runtime for the server
- npm - node package manager
- Babel - ES2015 and later to ES5 JavaScript compiler (see below)
- Mocha - testing framework
- Should - assertion library
- ESLint - static analysis of code for JavaScript
- nyc - a command line interface for istanbul compatible with ES2015
- JSDoc - documentation utility
Babel packages and plugins:
- babel-cli - command line interface
- babel-core - API for Node
- babel-plugin-istanbul - for test coverage with nyc
- babel-plugin-transform-runtime - for polyfilling libraries
- babel-preset-es2015 - default transforms
- babel-register - require hook for testing with Mocha
- babelify - Browserify transform
What the name of this library means
The word "maryamyriameliamurphies" occurs on pg. 293 of James Joyce's Finnegans Wake. The two brothers Kev and Dolph (surrogates for the archetypal Shem and Shaun, who represent all rival brothers in history and myth) are having a math lesson. Dolph, the elder, is attempting to explain to Kev the first postulate of Euclid, which results in a rather prurient diagram of circles and triangles. Happily for me, as a functional programmer, it contains a λ. If you want to find out about the naughtier significances of this diagram, you'll have to research that for yourself (hint: like functional programming, it involves "lifting"). In the middle of Dolph's explanation, Kev starts to daydream, hence all the invocations of "murphy," an allusion to Morpheus, the Greek god of dreams (also the common Irish surname, Murphy, as well as a slang word meaning both "potato" and "confidence game").
Here's my own interpretation of maryamyriameliamurphies:
- mary — A variant of the interjection "marry" common during the early modern period. It expresses surprise or outrage, more or less equivalent to "wow!" Also a mild oath, since it refers to the Virgin Mary (as pure as a monadic interface, she was).
- myria — Probably the word myriad, "many people or things." Also the ancient Greek word for 10,000, though used just as often to mean an uncountably large number of things (because who would ever need to count higher than 10,000?).
- melia — Similar to the Latin word for a thousand (mille), but it also looks to me like the plural of the Greek word for "honey," which can also be used to describe something sweet (or, at a stretch, the Latin word "meliora" meaning "better than").
- murphies — As an allusion to Morpheus, refers to the Greek word for "form" since dreaming is an experience of many forms shifting and changing. A "morphism" is also another word for a "mapping" or "function" in various branches of mathematics, though it's doubtful this would have occurred to Joyce.
maryamyriameliamurphies — Wow, a whole bunch of sweet functions!
API
See the online documentation for more extensive explanations and examples. The online docs can also be generated locally with JSDoc if you git clone or npm install this repo.
Basic functions
See Haskell Data.Function and Prelude.
partial(f, ...as)Partially applies argumentsasto functionf.$(f)Composes functionfwith another functiong.flip(f)Reverses the order of arguments to a function.id(a)Returnsa. The identity function.constant(a, b)Returnsa, discardingb.until(pred, f, x)Applyftoxuntilpredis true.and(a, b)Boolean "and".or(a, b)Boolean "or".not(a)Boolean "not".even(a)Return true ifais even.odd(a)Return true ifais odd.isEmpty(a)Return true ifais an empty list, tuple, or array.show(a)Return a string representation of a value (for data types from this library).print(a)Display the results ofshowon the console.
Eq
See Haskell Eq.
isEq(a, b)Returns true ifaequalsb.isNotEq(a, b)Returns true ifadoes not equalb.
Ord
See Haskell Ord.
EQOrdering for equals.LTOrdering for less than.GTOrdering for greater than.compare(a, b)Return the Ordering ofaas compared tob.lessThan(a, b)Return true ifais less thanb.lessThanOrEqual(a, b)Return true ifais less than or equal tob.greaterThan(a, b)Return true ifais greater thanb.greaterThanOrEqual(a, b)Return true ifais greater than or equal tob.max(a, b)Return the greater ofaandb.min(a, b)Return the lesser ofaandb.
Monoid
See Haskell Monoid.
mempty(a)Return the identity for the monoid.mappend(a, b)Perform an associative operation on two monoids.mconcat(a)Fold a list using the monoid.
Functor
See Haskell Functor.
fmap(f, a)Map the functionfover the functora.fmapReplaceBy(a, b)Replace allbwithain a functor.
Applicative
See Haskell Applicative.
pure(f, a)Liftainto applicative contextf.ap(f, a)Apply applicative functionfto applicative valuea.apFlip(f, a, b)apwith its arguments reversed.then(a1, a2)Sequence actions, discarding the value ofa1.skip(a1, a2)Sequence actions, discarding the value ofa2.liftA(f, a)Lift functionfinto applicative contexta.liftA2(f, a, b)Lift binary functionfinto applicative contexta.
Monad
See Haskell Monad.
inject(m, a)Inject valueainto monadic contextm.flatMap(m, f)Bind functionfto the value contained in monadic contextm.chain(m, f)Sequence actions, ignoring the value of the monadic contextm.bindFlip(f, m)bindwith its arguments reversed.join(m)Remove one level of monadic structure, likeconcat.liftM(f, m)Lift a functionfinto monadic contextm.Do(m)Wrap a monadmin a special container for the purpose of chaining actions, in imitation of Haskell's "do" notation.
Foldable
See Haskell Foldable.
fold(a)Combine the elements of a structure using a monoid.foldMap(f, a)Mapfto each element in monoida.foldr(f, z, t)Fold functionfover monoidtwith accumulatorz.
Traversable
See Haskell Traversable.
traverse(f, a)Mapfover each element in monoidaand collect the results of evaluating each action.mapM(f, m)traversefor monads.sequence(m)Evaluate each action in monadic structuremand collect the results.
Maybe
See Haskell Maybe.
just(a)Insert a value into a Maybe monad, returningJust aorNothing.maybe(n, f, m)Applyfto the value in Maybemor returnnifmisNothing.isMaybe(a)Return true ifais a Maybe.isJust(m)Return true if MaybemisJust.isNothing(m)Return true if MaybemisNothing.fromJust(m)Extract the value from Maybem, throwing an error ifmisNothing.fromMaybe(d, m)Extract the value from Maybem, returningdifmisNothing.listToMaybe(as)ReturnNothingon an empty list orJust awhereais the first element of the list.maybeToList(m)Return an empty list ifmifNothingor a singleton listaifmisJust a.catMaybes(as)Return a list of allJustvalues from a list of Maybes.mapMaybe(f, as)Mapf(that returns a Maybe) over a list and return a list of eachJustresult.
Tuple
See Haskell Tuple.
tuple(...as)Create a new tuple from any number of values.fst(p)Return the first element of a tuple.snd(p)Return the second element of a tuple.curry(f, x, y)Convertftaking argumentsxandyinto a curried function.uncurry(f, p)Convert curried functionftaking argument tuple pairpinto an uncurried function.swap(p)Swap the first two values of a tuple.isTuple(a)Return true ifais a tuple.fromArrayToTuple(a)Convert an array into a tuple.fromTupleToArray(p)Convert a tuple into an array.
List
See Haskell List.
Basic functions
list(...as)Create a new list from a series of values.listRange(start, end, f, filter)Create a new list fromstarttoendusing step functionfwith values optionally filtered byfilter.listFilter(start, end, filter)Create a new list of consecutive values, filtered usingfilter.listRangeLazy(start, end)Create a new list of consecutive values fromstarttoend, using lazy evaluation.listRangeLazyBy(start, end, step)Create a new list fromstarttoendincremented by step functionstep, using lazy evaluation.listAppend(as, bs)Append listasto listbs.cons(x, xs)Create a new list with headxand tailxs.head(as)Extract the first element of a list.last(as)Extract the last element of a list.tail(as)Extract the elements of a list after the head.init(as)Extract all elements of a list except the last one.uncons(as)Decompose a list into its head and tail.empty(t)Test whether a foldable structure is empty.length(as)Return the length of list.isList(a)Return true ifais a list.fromArrayToList(a)Convert an array into a list.fromListToArray(as)Convert a list into an array.fromListToString(as)Convert a list into a string.fromStringToList(as)Convert a string into a list.
List transformations
map(f, as)Map the functionfover the elements in listas.reverse(as)Reverse the elements of a list.intersperse(sep, as)Intersperse the separatorsepbetween the elements ofas.intercalate(xs, xss)Intersperse the listxsbetween the lists inxss(a list of lists).transpose(lss)Transpose the "rows" and "columns" of a list of lists.
Reducing lists
foldl(f, z, as)Fold a listasfrom right to left, using functionfand accumulatorz.
Special folds
concat(xss)Concatenate the elements in a list of lists.concatMap(f, as)Map the functionf(that returns a list) over the listasand concatenate the result list.
Building lists
scanl(f, q, ls)Reduce a listlsfrom right to left using functionfand accumulatorqand return a list of successive reduced values.scanr(f, q0, as)Likescanlbut scans the listasfrom right to left.
Infinite lists
listInf(start)Return an infinite list of consecutive values beginning withstart.listInfBy(start, step)Return an infinite list of values, incremented with functionstep, beginning withstart.iterate(f, x)Return an infinite list of repeated applications offtox.repeat(a)Return an infinite list in which all the values area.replicate(n, x)Return a list of lengthnin which all values arex.cycle(as)Return the infinite repetition of a list.
Sublists
take(n, as)Return the prefix of a list of lengthn.drop(n, as)Return the suffix of a list after discardingnvalues.splitAt(n, as)Return a tuple in which the first element is the prefix of a list and the second element is the remainder of the list.takeWhile(pred, as)Return the longest prefix of a list of values that satisfy the predicate functionpred.dropWhile(pred, as)Drop values from a list while the predicate functionpredreturns true.span(pred, as)Return a tuple in which the first element is the longest prefix of a list of values that satisfy the predicate functionpredand the second element is the rest of the list.spanNot(pred, as)Return a tuple in which the first element is the longest prefix of a list of values that do not satisfy the predicate functionpredand the second element is the rest of the list.stripPrefix(as, bs)Drop the prefixasfrom the listbs.group(as)Take a list and return a list of lists such that the concatenation of the result is equal to the argument. Each sublist in the result contains only equal values.groupBy(eq, as)Take a list and return a list of lists such that the concatenation of the result is equal to the argument. Each sublist in the result is grouped according to functioneq.
Searching
lookup(key, assocs)Look upkeyin the association listassocs.filter(f, as)Return the list of elements fromasto satisfy the predicate functionf.
Indexing
index(as, n)Return the value inasat indexn.elemIndex(a, as)Return the index of the first value inasequal toaorNothingif there is no such value.elemIndices(a, as)Return the indices of all values inasequal toa, in ascending order.find(pred, xs)Return the first value inxsthat satisfies the predicate functionpredorNothingif there is no such value.findIndex(pred, xs)Return the index of the first value inxsthat satisfies the predicate functionpredorNothingif there is no such value.findIndices(pred, xs)Return the indices of all values inxsthat satisfypred, in ascending order.
Zipping and unzipping lists
zip(as, bs)Take two lists and return a list of corresponding pairs.zip3(as, bs, cs)zipfor three lists.zipWith(f, as, bs)Zipasandbsusing functionf.zipWith3(f, as, bs, cs)Zip three lists using functionf.
"Set" operations
nub(as)Remove duplicate values from a list.nubBy(eq, as)Remove duplicate values from a list, testing equality using functioneq.deleteL(a, as)Remove the first occurrence ofafromas.deleteLBy(eq, a, as)Remove the first occurrence ofafromas, testing equality using functioneq.deleteFirsts(as, bs)Remove the first occurrence of each value ofasfrombs.deleteFirsts(eq, as, bs)Remove the first occurrence of each value ofasfrombs, using functioneqto test for equality.
Ordered lists
sort(as)Sort a list.sortBy(cmp, as)Sort a list using comparison functioncmp.mergeSort(as)Sort a list using a merge sort algorithm.mergeSortBy(cmp, as)Merge sort a list using comparison functioncmp.insert(e, ls)Inserteat the first position inlswhere it is less than or equal to the next element.insertBy(cmp, e, ls)Inserteat the first position inlsusing comparison functioncmp.
Utility functions
throwError(e)Throws an error with messagee.defines(...methods)Defines a type class that requires implementations ofmethods.dataType(a)Returns the data type ofa(a synonym fora.constructor).type(a)Returns the type ofaas defined by this library ortypeofotherwise.typeCheck(a, b)Checks whetheraandbare the same type.
