fp
My little functional programming library. Just a few functions I don't like
re-writing. I am slowly adding tests, run with npm run test
and you should see
272 tests passing.
Features:
- Many utility functions, such as
compose
, pipe
, and curry
. - Some ADTs such as
Maybe
, Result
, and IO
- A simple reactive library for
Observable
, including methods like map
,
filter
, and reduce
.
Install
npm install @ebflat9/fp
View the npm package page
Functional Programming Examples
import * as fp from '@ebflat9/fp'
fp.identity(1)
const one = fp.constant(1)
one()
const fn = (a, b) => `${a}${b}`
fp.flip(fn)('hello', 'world')
const fn = (...args) => [...args]
fp.unary(fn)(1, 2, 3)
const toUpperCase = demethodize(String.prototype.toUpperCase)
toUpperCase('hi')
const obj = {
a: {
b: {
c: {
d: 1,
},
},
},
}
fp.deepProp('a.b.c.d', obj)
const obj = {
a: {
b: 2,
},
}
fp.deepSetProp('a.b', 3)(obj)
const obj = {
a: {
b: {
c: 'hi',
},
e: 'world',
},
h: 'sup',
}
fp.deepPick(['a.b.c', 'a.e'])(obj)
const obj = {
title: 'My book',
publication_date: 1987,
}
fp.rename({ publication_date: 'publicationDate' }, obj)
const a = {
title: 'my book',
author: 'tim',
publication_date: 2008,
}
const b = {
title: 'my book',
publication_date: 1987,
author: 'dave',
}
fp.aggregateOn({ author: 'authors', publication_date: 'publicationDates' }, a, b)
const a = [
{
name: 'tim',
age: 15,
},
{
name: 'tim',
age: 5,
},
{
name: 'bob',
age: 87,
},
]
fp.groupBy('name', a)
const arr = [{ name: 'tim' }, { name: 'bob' }]
fp.keyBy('name', arr)
const a = [
{
isbn: '978-0812981605',
title: '7 Habits of Highly Effective People',
available: true,
},
{
isbn: '978-1982137274',
title: 'The Power of Habit',
available: false,
},
]
const b = [
{
isbn: '978-0812981605',
title: '7 Habits of Highly Effective People',
subtitle: 'Powerful Lessons in Personal Change',
number_of_pages: 432,
},
{
isbn: '978-1982137274',
title: 'The Power of Habit',
subtitle: 'Why We Do What We Do in Life and Business',
subjects: ['Social Aspects', 'Habit', 'Change (Psychology)'],
},
]
fp.deepJoin('isbn', 'isbn', a, b)
MultiMethod
A multimethod is a function that decides which handler to call based on its
arguments. It is a way to create polymorphism without classes.
const store = {
todos: [],
add(todo) {
this.todos.push({ text: todo, id: this.todos.length + 1 })
return this
},
remove(id) {
this.todos = this.todos.filter(td => td.id !== id)
return this
},
}
const dispatch = fp.multi(
(action, store) => action.type,
fp.method('ADD_TODO', (action, store) => store.add(action.text)),
fp.method('REMOVE_TODO', (action, store) => store.remove(action.id))
)
dispatch({ type: 'ADD_TODO', text: 'Hello world' }, store)
dispatch({ type: 'REMOVE_TODO', id: 1 }, store)
const a = fp.multi(fp.method('a', () => 'b'))
const upper = a.map(s => s.toUpperCase())
upper('a')
const router = fp.multi(
fp.method(req => ['GET'].includes(req.method) && req.url === '/', 'OK'),
fp.method(
req => ['GET', 'POST'].includes(req.method) && req.url === '/users',
[{ id: 1, name: 'John' }]
),
fp.method('Unknown endpoint')
)
router({ method: 'GET', url: '/' })
Observable
An Observable is a way to abstract asynchronous and synchronous events in
a way that makes it easier to work with, and more consistent.
Observable.from([1, 2, 3]).subscribe(console.log)
Observable.of(1, 2, 3).subscribe(console.log)
Observable.fromPromise(
new Promise(resolve => setTimeout(() => resolve('hi'), 1))
).subscribe(console.log)
Observable Operators
Various operations are available, such as:
Observable.from([1, 2, 3])
.map(x => x * x)
.subscribe(console.log)
Observable.from([1, 2, 3])
.filter(n => n % 2 !== 0)
.subscribe(console.log)
Observable.from([1, 2, 3]).take(2).subscribe(console.log)
Observable.from([1, 2, 3]).skip(2).subscribe(console.log)
Observable.from([1, 2, 3])
.concat(Observable.from(['a', 'b', 'c']))
.subscribe(console.log)
Observable.from([1, 2, 3])
.combine(Observable.from(['a', 'b', 'c']))
.subscribe(console.log)
Observable.from([1, 2, 3])
.flatMap(x => Observable.from([1, 2, 3].map(y => x + y)))
.subscribe(console.log)
Observable.from([{ name: 'tim' }, { name: 'bob' }])
.pick('name')
.subscribe(console.log)
Observable.from([1, 2, 2, 3]).distinct().subscribe(console.log)
Observable.from([1, 2, 3, 4, 5])
.until(n => n > 3)
.subscribe(console.log)
Observable.from([1, 2, 3])
.zip(Observable.from(['a', 'b', 'c']))
.subscribe(console.log)
Observable Subjects
A subject can act as an observable and an observer:
const values = []
const stream = Observable.subject()
stream
.map(x => x * x)
.filter(x => x % 2 === 0)
.subscribe({
next: value => values.push(value),
})
Observable.from([1, 2, 3, 4, 5, 6]).subscribe(stream)
Observable Sharing
Share an async (hot) or sync (cold) stream:
const values = []
const values2 = []
const stream = Observable.from([1, 2, 3, 4]).share()
stream.subscribe({
next: value => values.push(value),
})
stream.subscribe({
next: value => values2.push(value),
})
Store
My attempt to write a simple Redux clone.
import { Reducer, createStore } from '@ebflat9/fp'
const reducer = Reducer.builder()
.case('ADD', (state, action) => ({
...state,
value: action.payload,
}))
.init({ value: null })
.build()
const store = createStore(reducer)
store
.observe()
.map(state => state.value && state.value.toUpperCase())
.subscribe(console.log)
store.dispatch({ type: 'ADD', payload: 'hello' })
Creating an Async Thunk
import { createAsyncThunk } from '@ebflat/fp'
const myThunk = createAsyncThunk('ADD', arg =>
new Promise(resolve) => setTimeout(() => resolve(arg), 1)
)
const myReducer = Reducer.builder()
.case(myThunk.fulfilled.type, (state, action) => ({
...state,
value: action.payload
}))
.init({value: null})
.build()
const myStore = createConfiguredStore(myReducer)
store.dispatch(myThunk('hello'))
store.observe().subscribe(console.log)
There are many more functions available. Check out the tests for further
clarification.