Skip to content
/ hu Public

Small, generic functional helper library for node.js and browsers

License

Notifications You must be signed in to change notification settings

h2non/hu

Repository files navigation

hu Build Status NPM version Total Downloads

hu is a functional-oriented generic utility helper library inspired by Haskell's prelude and Lodash/Underscore/Ramda.

Unlike other well-known libraries, hu only provides a reduced but very common set of useful functions. It aims to be a lightweight and small library which can be easily embedded as a part of an application, library or framework without making noise.

hu library works well in ES5 compliant engine. Most of its functions are implicitly curried.

Features

  • Complete and reliable type checking helpers
  • Collection and object iterators
  • Array and objects processors and transformers helpers
  • Functional composition and async (curry, partial, compose, memoize, defer...)
  • String manipulation helpers
  • Equality comparison functions, including deep comparison
  • Runs in node.js and browsers
  • Well tested with 100% of coverage
  • Small (~800 SLOC)
  • Dependency free

Installation

Node.js

$ npm install hu --save

Browser

Via Bower

$ bower install hu

Via Component

$ component install h2non/hu

Or load the script remotely (just for testing or development)

<script src="//cdn.rawgit.com/h2non/hu/0.1.2/hu.js"></script>

Browsers Support

Chrome Firefox IE Opera Safari
+5 +3.5 +9 +10.6 +5

Why hu?

Motivation

Functional programming tendency has grown (again) in the last years. This paradigm, unlike others that may be more totalitarian and extended, forces a radical re-thinking in the way that programmers design and implement software.

Functional programming thinking and conversion is not always easy to apply, but it's really a quite attractive and funny paradigm which could helps a lot when solving certaing kind of problems in a more theoretically and conceptually clean way, and tipically more efficiently.

Rationale

A good approach to learn and apply functional programming principles is creating a functional-oriented library that assists by itself to do functional programming

In fact, hu was created to define a real scenario to do funny experiments with some pure functional programming principles

It's completely written in Wisp, a homoiconic Clojure-like language, which compiles into plain JavaScript that supports s-expressions and macros, allowing to extend the compiler features with the user’s own code

So... why JavaScript?

JavaScript is an ubiquitous, well-extended, multi-purpose and multi-paradigm cool language with which you can do a lot of funny things

Yes, I know, JavaScript is not a pure functional language, however its natural extensibility and meta-programming capabilities allows you to apply different paradigms to it and today there are a lot of languages that transpile into JavaScript that help providing a powerful syntax sugar and more features, like in this case using Wisp.

Challenges

hu is implemented keeping in mind the following “ambitious” functional focused goals:

  • Assume it's a first-class function only language
  • Pure functions as a norm
  • Immutability (when it's possible)
  • Avoid assignment, remove side-effects (when it's possible)
  • Tend to recursion instead of loops
  • Tend to high-order functions
  • Tend to functional composition
  • Tend to continuation-passing style
  • Exploit subroutines (like tail recursion call)
  • Exploit function memorization (currying, partial, caching...)
  • Macros are a great thing when used right¡, don't have fear.

API

Overview example

JavaScript Harmony (ES6)

var { log, filter, even, inc } = require('hu')

log(map(filter({a: 1, b: 2}, even), inc))
// → { b: 3 }

Or with the funny LiveScript

(a: 1, b: 2)
  |> filter _, even
  |> map _, inc
  |> log

Type checking

isBool(value)

Return: boolean | Alias: isBoolean

Checks if the given value is a boolean type

isNumber(value)

Return: boolean

Checks if the given value is a number type

isFinite(value)

Return: boolean

Checks if the given value is a finite number, or it can be coerced into it

isNaN(value)

Return: boolean

Is it NaN (not a number)? More accurate than the native isNaN function

isString(value)

Return: boolean

Checks if the given value is a string type

isSymbol(value)

Return: boolean

Checks if the given value is a symbol type

isFunction(value)

Return: boolean | Alias: isFn

Checks if the given value is a function type

isDate(value)

Return: boolean

Checks if the given value is a date type

isRegExp(value)

Return: boolean | Alias: isPattern

Checks if the given value is a regexp type

isArray(value)

Return: boolean

Checks if the given value is an array type

isObject(value)

Return: boolean

Checks if the given value is an object type

isPlainObject(value)

Return: boolean

Checks if the given value is a native object type (it was createdd by the Object native constructor)

isError(value)

Return: boolean

Checks if the given value is an error type

isElement(value)

Return: boolean

Checks if the given value is a DOM element object instance

isArgs(value)

Return: boolean | Alias: isArguments

Checks if the given value is an arguments object

isUndef(value)

Return: boolean | Alias: isUndefined

Checks if the given value is a undefined, void o null type

isNull(value)

Return: boolean

Checks if the given value is a null type

isEmpty(value)

Return: boolean

Checks if the given value is empty. Arrays, strings, or arguments objects with a length of 0 and objects with no own enumerable properties are considered empty values

notEmpty(value)

Return: boolean | Alias: isNotEmpty

Checks if the given value is not empty

isMutable(value)

Return: boolean

Checks if the given value is a mutable data type. Objects, arrays, date objects, arguments objects and functions are considered mutable data types

isPrimitive(value)

Return: boolean

Checks if the given value is a primitive value type. Strings, numbers, booleans, symbols and null are considered primitives values

isIterable(value)

Return: boolean | Alias: canIterate

Checks if the given value can be iterated. Objects, arrays, and arguments objects are considered iterables data types

Strings

subs(str, start, end)

Return: string

Extract characters from the given string

lines(str)

Return: array

Split the given string by end of line tokens

unlines(arr)

Return: string

Join the given array into a string separated by end line token

words(str)

Return: array

Returns an array of words (spaces separated)

unwords(arr)

Return: string

Join words of the given array into a string spaces separated

chars(str)

Return: array

Return an array of characters of the given string

unchars(arr)

Return: string

Join the strings of the given array

char(number)

Return: string

Return the given unicode number into his equivalent character

reverse(str)

Return: string

Reverse characters of the given string

repeat(number, str)

Return: string

Repeat the given string

escape(str)

Return: string

Converts the characters &, <, >, ", and ' in the given string to their corresponding HTML entities

Numbers

odd(number)

Return: boolean | Alias: isOdd

Returns true if the given number is odd

even(number)

Return: boolean | Alias: isEven

Returns true if the given number is even

lower(x, y)

Return: boolean | Alias: isLower | Curried: true

Returns true if x it's lower than y

higher(x, y)

Return: boolean | Alias: isHigher | Curried: true

Returns true if x it's lower than y

max(...numbers)

Return: number

Returns the number with the highest value

min(...numbers)

Return: number

Returns the number with the lower value

inc(number)

Return: number

Increment the given value

dec(number)

Return: number

Decrement the given value

signum(number)

Return: number

Takes a number and returns either -1, 0, or 1 depending on the sign of the number

isNegative(number)

Return: boolean

Returns true if the given number is negative

negate(number)

Return: number

The negation of the given number

recip(number)

Return: number

One over the number: ie 1 / x

div(number)

Return: number

Division truncated down toward negative infinity

max(...numbers)

Return: number

Returns the largest of zero or more numbers

min(...numbers)

Return: number

Returns the smallest of zero or more numbers

abs(number)

Return: number

Returns the absolute value of a number

round(number)

Return: number

Returns the value of a number rounded to the nearest integer

random()

Return: number

Returns a pseudo-random number between 0 and 1

floor(number)

Return: number

Returns the largest integer less than or equal to a number

sin(number)

Return: number

Returns the sine of a number

tan(number)

Return: number

Returns the tangent of a number

cos(number)

Return: number

Returns the cosine of a number

asin(number)

Return: number

Returns the arcsine of a number

atan(number)

Return: number

Returns the arctangent of a number

atan2(number)

Return: number

Returns the cosine of a number

ceil(number)

Return: number

Returns the smallest integer greater than or equal to a number

exp(number)

Return: number

Returns Ex, where x is the argument, and E is Euler's constant (2.718...), the base of the natural logarithm

sqrt(number)

Return: number

Returns the positive square root of a number

PI

Type: number

Ratio of the circumference of a circle to its diameter, approximately 3.14159

Arrays

inArray(arr, element)

Return: boolean | Curried: true

Checks if an element exists in the given array

head(arr)

Return: mixed | Alias: first

First item of the given array

hu.head([1, 2, 3]) // → 1

tail(arr)

Return: array | Alias: rest

Everything but the first item of the list

hu.tail([1, 2, 3]) // → [2, 3]

last(arr)

Return: mixed | Alias: end

The last item of the list

hu.last([1, 2, 3]) // → 3

initial(arr)

Return: array

Everything but the last item of the list

hu.initial([1, 2, 3]) // → [1, 2]

flatten(arr)

Return: array

Recursively flatten elements of a multidimensional list into a one dimension list.

hu.flatten([1, [2], [3, [4, [5]]]]) // → [1, 2, 3, 4, 5]

Objects

has(obj, property)

Return: boolean

Checks if the specified property name exists as a own property of the given object

hu.has({a: true}, 'a') // → true

keys(obj)

Return: array

Returns a sequence of the map's keys

hu.keys({a: true}) // → ['a']

vals(obj)

Return: array

Returns a sequence of the map's values

hu.vals({a: true}) // → [true]

keyValues(obj)

Return: array | Alias: pairs

Returns a two dimensional array of an object’s key-value pairs

hu.keyValues({a: true}) // → [['a', true]]

toObject(obj)

Return: array

Creates an object of given arguments. Odd indexed arguments are used for keys and evens for values

hu.toObject('a', true) // → {a: true}

extend(target, ...origins)

Return: object | Alias: assign

Assigns own enumerable properties of source object(s) to the destination object

hu.extend({x: true}, {y: false}) // → {x: true, y: false}

mixin(target, ...origins)

Return: object

Adds function properties of a source object to the destination object

var methods = {
  something: function () {
    // cool stuff
  }
}
hu.mixin({x: true}, methods)
// → {x: true, something: function () {}}

map(obj, function)

Return: object | Alias: mapValues | Curried: true

Maps object values by applying with the value return of each callback call on each one

function mapper(val) {
  return val * 2
}
hu.map({x: 2}, mapper) // → {x: 4}

filter(obj, function)

Return: object | Alias: filterValues | Curried: true

Iterates over properties of an object, returning an filtered new object of all elements where the callback returns true

function filter(val) {
  return val > 1
}
hu.map({x: 1, y: 2}, filter) // → {y: 2}

clone(object)

Return: object

Creates a clone of the given object

var obj = {x: 1}
var newObj = hu.clone(obj)
newObj === obj // → false

merge(x, y)

Return: object

Similar to extend, it returns an object that consists of the rest of the maps conj-ed onto the first

If a key occurs in more than one map, the mapping from the latter (left-to-right) will be the mapping in the result

var obj1 = {x: {y: {z: 2}}}
var obj2 = {x: {y: {a: 1}}}
var newObj = hu.merge(obj1, obj2)
// → {x: {y: {z: 2, a: 1}}}

Collections

each(obj, function)

Return: object | Alias: forEach

Iterates over elements of an iterable object, executing the callback for each element. It will return the same given object

hu.each([1, 2], function (n) {
  console.log('Value:', n)
})

size(obj)

Return: number

Gets the size of the given collection

hu.size({x: 1, y: 2}) // → 2

compact(obj)

Return: object|array | Alias: clean

Returns a new collection which contains only the not empty values

hu.compact([1, null, undefined, "", 5])
// → [1, 5]

Functions

constant(value)

Return: function | Alias: identity

Returns a function that returns the given value

var getter = hu.constant('john')
getter() === 'john' // → true

apply(fn, args)

Return: mixed

Invokes a function binding itself function object context with the given arguments as array

function myFn(x, y) { return x * y }
hu.apply(myFn, [2, 2]) // → 4

bind(fn, ctx)

Return: function

Creates a function that, when called, invokes the function with the this binding of thisArg and prepends any additional bind arguments to those provided to the bound function

function func(greeting) {
  return greeting + ' ' + this.name
}
func = hu.bind(func, { 'name': 'john' }, 'hi')
func() // → 'hi john'

partial(fn, [ ...partialArgs ])

Return: function

Creates a function that, when called, invokes func with any additional partial arguments prepended to those provided to the new function

function greet(greeting, name) {
  return greeting + ' ' + name;
}
var hi = hu.partial(greet, 'hi');
hi('john') // → 'hi john'

curry(fn, [ ctx ])

Return: function

Creates a function which accepts one or more arguments of the given function that when invoked either executes the function returning its result

var curried = hu.curry(function(a, b, c) {
  console.log(a + b + c)
})
curried(1)(2)(3) // → 6
curried(1, 2)(3) // → 6
curried(1, 2, 3) // → 6

compose(...fn)

Return: function

Creates a function that is the composition of the provided functions, where each function consumes the return value of the function that follows

function name(name) {
  return name.toLowerCase() + '!'
}
function greet(name) {
  return 'Hi ' + name
}
var welcome = hu.compose(name, greet);
welcome('John') // → 'Hi john!'

memoize(fn, resolver)

Return: function

Creates a function that memoizes the result of the the given function. If resolver is provided it will be used to determine the cache key for storing the result based on the arguments provided to the memoized function. The resolver function just uses the first argument to the memoized function as the key

var multiply = hu.memoize(function (n) {
  return n * 2
})
multiply(2) // → 4 (computed value)
multiply(2) // → 4 (memoized value)

With custom resolver function to define memoized values

var multiply = hu.memoize(function (n) {
  return n * 2
}, function (n) {
  return n === 2 ? n + 1 : n
})
multiply(1) // → 2 (computed value)
multiply(2) // → 4 (computed value)
multiply(3) // → 4 (memoized value, from 2 value)

wrap(fn, wrapperFn, [ ...args ])

Return: function | Curried: true

Creates a function that provides value to the wrapper function as its first argument. Additional arguments provided to the function are appended to those provided to the wrapper function

function hello(name) {
  return "hi " + name;
}
hello = hu.wrap(hello, function (fn, text, type) {
  return "before, " + fn("moe") + ", " + text + ": " + type + ", after"
}, "type")
hello("salutation") // → 'before, hi moe, type: salutation, after'

once(fn)

Return: function

Creates a function that is restricted to execute function once time. Subsuquents calls to the function will return the memoized value of the initial call

var times = 0
var init = hu.once(function () {
  return times += 1
})
init() // → 1
init() // → 1

throttle(fn)

Return: function | Curried: true

Creates a function that, when executed, will only call the fn function at most once per every wait milliseconds

var test = hu.throttle(function () {
  console.log(Date.now())
}, 100)
test() // → first call
test() // → no call
setTimeout(test, 150) // → second call

times(fn, number)

Return: function | Curried: true

Creates a function that is restricted to be executed a finite number of times. Subsuquents calls to the function will return the memoized value of the latest call

var times = 0
var init = hu.times(function () {
  return times += 1
}, 2)
init(); // → 1
init(); // → 2
init(); // → 2

defer(fn, ms, [ ...args ])

Return: void

Executes the given function after wait milliseconds. You can provide arguments that will be passed to the function when it's invoked

function delayed(text) {
  console.log(text)
}
hu.defer(delayed, 1000, 'later')
// → logs 'later' after one second

debounce(fn, ms, [ ...args ])

Return: function

Return a function that executes the given function after wait milliseconds when it's called. You can provide arguments that will be passed to the function when it will be invoked

function delayed(text, name) {
  console.log(text, name)
}
var lazy = hu.debounce(delayed, 1000, 'later')
lazy('call') // → logs 'later call' after one second

Equality

isEqual(x, y)

Return: boolean | Alias: equal, deepEqual, 'isDeepEqual'

Compares primitives types and data objects in a type-independent manner. Clojure's immutable data structures define -equiv (and thus =) as a value, not an identity, comparison.

isPatternEqual(x, y)

Return: boolean | Alias: isRegExpEqual, patternEqual | Curried: true

Check if the given dates are equal

isDateEqual(x, y)

Return: boolean | Alias: dateEqual | Curried: true

Check if the given dates are equal

isArrayEqual(x, y)

Return: boolean | Alias: arrayEqual | Curried: true

Check if the given arrays has the same elements

isObjectEqual(x, y)

Return: boolean | Alias: objectEqual | Curried: true

Checks if the given objects values and keys are equals

Miscellaneous

log(...msg)

Return: undefined

Write the given arguments in the console

isBrowser

Type: boolean

Checks if the current runtime JavaScript environment is in a browser context

noop()

Return: void

The no-operation function, that returns void

now()

Return: number

Returns an integer timestamp for the current time

_global()

Return: object

Environment specific global object

Contributing

Wanna help? Cool! It will be really apreciated :)

You must add new test cases for any new feature or refactor you do, always following the same design/code patterns that already exist

Tests specs are completely written in Wisp language. Take a look to the language documentation if you are new with it. You should follow the Wisp language coding conventions

Development

Only node.js is required for development

Clone/fork this repository

$ git clone https://github.com/h2non/hu.git && cd hu

Install package dependencies

$ npm install

Compile code

$ make compile

Run tests

$ make test

Browser sources bundle generation

$ make browser

Release a new version

$ make release

License

Copyright (c) Tomas Aparicio

Released under the MIT license

Bitdeli Badge