Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/astridlyre/fp
fp - My little functional programming library
https://github.com/astridlyre/fp
functional-programming iterable javascript multimethods observable stream
Last synced: 2 days ago
JSON representation
fp - My little functional programming library
- Host: GitHub
- URL: https://github.com/astridlyre/fp
- Owner: astridlyre
- License: mit
- Created: 2021-09-29T06:10:06.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2023-02-23T23:55:59.000Z (over 1 year ago)
- Last Synced: 2024-11-09T04:20:33.579Z (7 days ago)
- Topics: functional-programming, iterable, javascript, multimethods, observable, stream
- Language: TypeScript
- Homepage:
- Size: 1.17 MB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# 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
```bash
npm install @ebflat9/fp
```[View the npm package page](https://www.npmjs.com/package/@ebflat9/fp)
## Functional Programming Examples
```javascript
import * as fp from '@ebflat9/fp'// identity x => x
fp.identity(1) // 1// constant x => _ => x
const one = fp.constant(1) // () => 1
one() // 1// flip2, flips the order of arguments
const fn = (a, b) => `${a}${b}`
fp.flip(fn)('hello', 'world') // 'worldhello'// unary, convert a function to one taking a single argument
const fn = (...args) => [...args]
fp.unary(fn)(1, 2, 3) // [1]// demethodize, convert a method to a regular function
const toUpperCase = demethodize(String.prototype.toUpperCase)
toUpperCase('hi') // 'HI'// deepProp, get a prop using a path
const obj = {
a: {
b: {
c: {
d: 1,
},
},
},
}
fp.deepProp('a.b.c.d', obj) // 1// deepSetProp, set a prop using a path (returns a copy)
const obj = {
a: {
b: 2,
},
}
fp.deepSetProp('a.b', 3)(obj) // { a: { b: 3 } }// deepPick, pick only keys from paths in an object
const obj = {
a: {
b: {
c: 'hi',
},
e: 'world',
},
h: 'sup',
}
fp.deepPick(['a.b.c', 'a.e'])(obj) // { a: { b: { c: 'hi' }, e: 'world' } }// rename, using a keymap, rename the keys in an object
const obj = {
title: 'My book',
publication_date: 1987,
}
fp.rename({ publication_date: 'publicationDate' }, obj)
// {title: 'My book', publicationDate: 1987 }// aggregateOn, combine all properties from two objects into one, rightmost object
// wins in case of duplicate properties, keymap properties are combined into an
// array of unique values.
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)
// { title: 'my book', authors: ['tim', 'dave'], publicationDates: [2008, 1987] }// groupBy, partition an array of objects into groups by key
const a = [
{
name: 'tim',
age: 15,
},
{
name: 'tim',
age: 5,
},
{
name: 'bob',
age: 87,
},
]
fp.groupBy('name', a)
// [[{name: 'tim', age: 15}, {name: 'tim', age: 5}], [{name: 'bob', age: 87}]]// keyBy, convert an array to an object
const arr = [{ name: 'tim' }, { name: 'bob' }]
fp.keyBy('name', arr) // { tim: { name: 'tim' }, bob: {name: 'bob' } }// deepJoin, combine two arrays
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)
/* [
* {
* available: true,
* isbn: '978-0812981605',
* number_of_pages: 432,
* subtitle: 'Powerful Lessons in Personal Change',
* title: '7 Habits of Highly Effective People',
* },
* {
* available: false,
* isbn: '978-1982137274',
* subjects: ['Social Aspects', 'Habit', 'Change (Psychology)'],
* subtitle: 'Why We Do What We Do in Life and Business',
* title: 'The Power of Habit',
* },
*]
*/
```## 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.```javascript
// multiMethod
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)
// store.todos = [{ text: 'Hello world', id: 1 }]
dispatch({ type: 'REMOVE_TODO', id: 1 }, store)
// store.todos = []// mapping a multiMethod
const a = fp.multi(fp.method('a', () => 'b'))
const upper = a.map(s => s.toUpperCase())
upper('a') // 'B'// use functions as keys
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: '/' }) // 'OK'
```## 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.```javascript
// Create an Observable
Observable.from([1, 2, 3]).subscribe(console.log) // 1, 2, 3Observable.of(1, 2, 3).subscribe(console.log) // 1, 2, 3
Observable.fromPromise(
new Promise(resolve => setTimeout(() => resolve('hi'), 1))
).subscribe(console.log) // 'hi'
```### Observable Operators
Various operations are available, such as:
```javascript
// Map
Observable.from([1, 2, 3])
.map(x => x * x)
.subscribe(console.log) // 1, 4, 9// Filter
Observable.from([1, 2, 3])
.filter(n => n % 2 !== 0)
.subscribe(console.log) // 1, 3// Take
Observable.from([1, 2, 3]).take(2).subscribe(console.log) // 1, 2// Skip
Observable.from([1, 2, 3]).skip(2).subscribe(console.log) // 3// Concat
Observable.from([1, 2, 3])
.concat(Observable.from(['a', 'b', 'c']))
.subscribe(console.log) // [1, 2, 3, 'a', 'b', 'c']// Combine
Observable.from([1, 2, 3])
.combine(Observable.from(['a', 'b', 'c']))
.subscribe(console.log) // [3, 'a']// flatMap
Observable.from([1, 2, 3])
.flatMap(x => Observable.from([1, 2, 3].map(y => x + y)))
.subscribe(console.log) // [2, 3, 4, 3, 4, 5, 4, 5, 6]// Pick
Observable.from([{ name: 'tim' }, { name: 'bob' }])
.pick('name')
.subscribe(console.log) // ['tim', 'bob']// Distinct
Observable.from([1, 2, 2, 3]).distinct().subscribe(console.log) // [1, 2, 3]// Until
Observable.from([1, 2, 3, 4, 5])
.until(n => n > 3)
.subscribe(console.log) // [1, 2, 3]// Zip
Observable.from([1, 2, 3])
.zip(Observable.from(['a', 'b', 'c']))
.subscribe(console.log) // [1, 'a'], [2, 'b'], [3, 'c']
```### Observable Subjects
A subject can act as an observable and an observer:
```javascript
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)
// values = [4, 16, 36]
```### Observable Sharing
Share an async (hot) or sync (cold) stream:
```javascript
const values = []
const values2 = []
const stream = Observable.from([1, 2, 3, 4]).share()stream.subscribe({
next: value => values.push(value),
})
// values = [1, 2, 3, 4]stream.subscribe({
next: value => values2.push(value),
})
// values2 = [1, 2, 3, 4]
```## Store
My attempt to write a simple Redux clone.
```javascript
import { Reducer, createStore } from '@ebflat9/fp'// Create a reducer
const reducer = Reducer.builder()
.case('ADD', (state, action) => ({
...state,
value: action.payload,
}))
.init({ value: null })
.build()// Create a store
const store = createStore(reducer)// Listen to updates
store
.observe()
.map(state => state.value && state.value.toUpperCase())
.subscribe(console.log)// Dispatch an update
store.dispatch({ type: 'ADD', payload: 'hello' }) // 'HELLO'
```### Creating an Async Thunk
```javascript
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) // { value: 'hello' }
```There are many more functions available. Check out the tests for further
clarification.