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
+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
- Type checking
- Strings
- Numbers
- Arrays
- Objects
- Collections
- Functions
- Equality
- Miscellaneous
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