1.2.63 • Published 2 years ago

@ebflat9/fp v1.2.63

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

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'

// 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.

// 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.

// Create an Observable
Observable.from([1, 2, 3]).subscribe(console.log) // 1, 2, 3

Observable.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:

// 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:

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:

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.

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

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.

1.2.60

2 years ago

1.2.63

2 years ago

1.2.61

2 years ago

1.2.62

2 years ago

1.2.52

2 years ago

1.2.53

2 years ago

1.2.56

2 years ago

1.2.57

2 years ago

1.2.54

2 years ago

1.2.55

2 years ago

1.2.58

2 years ago

1.2.51

2 years ago

1.2.8

3 years ago

1.2.7

3 years ago

1.2.6

3 years ago

1.2.12

2 years ago

1.2.13

2 years ago

1.2.10

3 years ago

1.2.11

2 years ago

1.2.16

2 years ago

1.2.17

2 years ago

1.2.14

2 years ago

1.2.15

2 years ago

1.2.9

3 years ago

1.2.41

2 years ago

1.2.42

2 years ago

1.2.40

2 years ago

1.2.45

2 years ago

1.2.46

2 years ago

1.2.43

2 years ago

1.2.44

2 years ago

1.2.49

2 years ago

1.2.47

2 years ago

1.2.48

2 years ago

1.2.50

2 years ago

1.2.18

2 years ago

1.2.19

2 years ago

1.2.20

2 years ago

1.2.23

2 years ago

1.2.24

2 years ago

1.2.21

2 years ago

1.2.22

2 years ago

1.2.27

2 years ago

1.2.28

2 years ago

1.2.25

2 years ago

1.2.26

2 years ago

1.2.29

2 years ago

1.2.30

2 years ago

1.2.31

2 years ago

1.2.35

2 years ago

1.2.32

2 years ago

1.2.33

2 years ago

1.2.38

2 years ago

1.2.39

2 years ago

1.2.36

2 years ago

1.2.37

2 years ago

1.2.0

3 years ago

1.1.29

3 years ago

1.1.28

3 years ago

1.2.5

3 years ago

1.2.4

3 years ago

1.2.3

3 years ago

1.2.2

3 years ago

1.2.1

3 years ago

1.1.30

3 years ago

1.1.34

3 years ago

1.1.33

3 years ago

1.1.32

3 years ago

1.1.31

3 years ago

1.1.38

3 years ago

1.1.37

3 years ago

1.1.36

3 years ago

1.1.35

3 years ago

1.1.39

3 years ago

1.1.41

3 years ago

1.1.40

3 years ago

1.1.45

3 years ago

1.1.44

3 years ago

1.1.43

3 years ago

1.1.42

3 years ago

1.1.49

3 years ago

1.1.48

3 years ago

1.1.47

3 years ago

1.1.46

3 years ago

1.1.15

3 years ago

1.1.14

3 years ago

1.1.19

3 years ago

1.1.17

3 years ago

1.1.23

3 years ago

1.1.22

3 years ago

1.1.21

3 years ago

1.1.20

3 years ago

1.1.27

3 years ago

1.1.26

3 years ago

1.1.25

3 years ago

1.1.24

3 years ago

1.1.70

3 years ago

1.1.74

3 years ago

1.1.73

3 years ago

1.1.72

3 years ago

1.1.78

3 years ago

1.1.77

3 years ago

1.1.76

3 years ago

1.1.75

3 years ago

1.1.79

3 years ago

1.1.52

3 years ago

1.1.51

3 years ago

1.1.50

3 years ago

1.1.56

3 years ago

1.1.55

3 years ago

1.1.54

3 years ago

1.1.53

3 years ago

1.1.59

3 years ago

1.1.58

3 years ago

1.1.57

3 years ago

1.1.63

3 years ago

1.1.62

3 years ago

1.1.61

3 years ago

1.1.60

3 years ago

1.1.67

3 years ago

1.1.66

3 years ago

1.1.65

3 years ago

1.1.69

3 years ago

1.1.68

3 years ago

1.1.1

3 years ago

1.0.19

3 years ago

1.1.0

3 years ago

1.0.18

3 years ago

1.0.17

3 years ago

1.1.9

3 years ago

1.1.8

3 years ago

1.1.7

3 years ago

1.1.6

3 years ago

1.1.5

3 years ago

1.1.4

3 years ago

1.1.3

3 years ago

1.1.2

3 years ago

1.1.12

3 years ago

1.1.11

3 years ago

1.0.21

3 years ago

1.1.10

3 years ago

1.0.20

3 years ago

1.1.13

3 years ago

1.0.16

3 years ago

1.0.9

3 years ago

1.0.8

3 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.11

3 years ago

1.0.10

3 years ago

1.0.15

3 years ago

1.0.14

3 years ago

1.0.13

3 years ago

1.0.12

3 years ago

1.0.2

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago