Write your programs as arrays. Because we all wish we were writing LISP or Clojure instead.
hx([map,
x => x + 1,
[1, 2, 3]]);
// ~> [2, 3, 4];
Hux is a declarative DSL for writing functional programs in javascript. It is modeled after the hiccup library in Clojure to provide an easy to write/read/modify language for writing transformations of data and views.
Hux provides a way to execute expressions, as well as a bunch of convenience functions for constructing functional programs and working with data structures. It is broken into two pieces:
- The executor function that takes in an array of the form
[func, parameter1, parameter2, ...]
and callsfunc
with each parameter passed into,func(parameter1, parameter2, ...)
. Example:
const { hx } = require('hux'); // hx is the "executor"
const array = [someFunction, "parameter1", { parameter: 2 }];
hx(array); // executes the expression
From now on we'll call these arrays "H-Expressions". Any array who's first element matches typeof element === 'function'
is treated as an H-Expression.
- A library of functions for doing common things
const { hx } = require('hux');
const { add, eq, ifelse, print, map } = require('hux/lib');
hx([ifelse, [eq, [add, 1, 2], [add, 2, 1]],
[print, "Math works!"],
[print, "WHO BROKE ADDITION?"]]); // ~> Logs "Math works!"
hx([map,
[add, 2], // the `add` function is curried if only one argument is passed
[1, 2, 3]]); // ~> [3, 4, 5]
Functions used with Hux are regular ol' JavaScript functions.
hx([x => x + 1, 1]) // same as `hx([inc, 1])` ~> 2
For instance, we could rewrite a simple version of one of the above examples like so:
const { hx } = require('hux');
// if value is true, returns left expression or value; else, returns the right expression/value
const ifelse = (value, left, right) => value ? left : right;
// returns true if all arguments are equal; else false
const eq = (first, ...rest) => rest.every(el => el === first);
// adds all parameters together
const add = (first, ...rest) =>
rest.length > 0 ? // if we have more than one number we're adding
first + add(...rest) : // add the first to the sume of the rest
first; // else return the first argument
const print = console.log.bind(console); // good ol' console.log as a function
// now use them in an expression
hx([ifelse, [eq, [add, 1, 2], [add, 2, 1]],
[print, "Math works!"],
[print, "WHO BROKE ADDITION?"]]);
The hx
function can also take in an array of plugins (somewhat like macros) to modify the behavior of how H-Expressions are parsed. For instance, the above example ifelse
written above actually evaluates both print
expressions; this is because the executor evaluates H-Expressions recursively. This allows us to treat parameters as values only in our functions. In the case of control-flow, we don't want this behavior, so instead we should use a plugin:
const { hx } = require('hux');
const { print } = require('hux/lib');
const ifelse = Symbol('ifelse');
const IfElsePlugin = {
predicate(f) { // predicate(f, ...args) returns true/false if plugin matches expression
return f === ifelse
},
executor(hx, ...expr) { // hx is the `hx` executor function with all plugins already applied
const [
_f, // === ifelse
predicate, // first argument; should evaluate to a truth-y or false-y value
left, // expression to evaluate if predicate is true
right // expression to evaluate if predicate is false
] = expr;
// evaluate the predicate; if truthy, evaluate the left; else, evaluate the right
return hx(predicate) ? hx(left) : hx(right);
}
}
hx([ifelse, true,
[print, "Will print"],
[print, "Won't print"]], [IfElsePlugin]);
Use with react-hyperscript
and hyperscript-helpers
to provide a hiccup-like DSL for creating dynamic JavaScript apps:
import h from 'react-hyperscript';
import { str, ifelse } from '../../../lib.js';
import { hx } from '../../../';
const { div, p, button } = require('hyperscript-helpers')(h);
export function App({ name, toggledOn, onClick }) {
return hx(
[div, [
[p, [str, "Hello, ", name, "!"]], // string concatenation
[p, [button, { onClick }, "Toggle"]],
[p, [ifelse, toggledOn,
"I'm toggled on!",
"I'm toggled off!"]]]]);
}
- Add more functional constructs (threading/piping, etc.) to
lib
- Finish tests for
lib
- Document all of
lib
- Add tests for
hx
Of course, after writing this I found that I wasn't the only one with such an idea. Here's some other projects that do something similar: