ramda-x

1.0.11 • Public • Published

Ramda X

Ramda X is a super small API with only 10 most important methods for functional programming. Original Ramda library is too big and abstracts almost everything away from the JavaScript language. That's why Ramda X uses the most important methods and leaves everything as it is. All implemented methods are auto-curried!!!

Important: Data comes last!

Implemented API methods:

  • I. curry()
  • II. compose()
  • III. map() -> works only on arrays
  • IV. filter() -> works only on arrays
  • V. prop() -> works only on objects prop('name')({name: 'Dimitri', location: 'Berlin'})
  • VI. propEq() -> works only on objects propEq('location', 'Berlin')
  • VII. reduce() -> works only on arrays reduce(fn, null || 'acc value', {location: Berlin})
  • VIII. Task() || Task.of(x) || Task.map(), Task.chain(), Task.ap() -> for lazy evaluation and isolation of side effects
  • IX. Either.Right(x) || Either.Left(x) || Either.fromNullable(x) || Either.of(x) || Either.try(f) -> for branching

Methods on the waiting list:

  • composeP() -> composition with Promises
  • propNotEq() -> removing a an element from an array

Additional methods for debugging Ramda X

  • trace() -> can be used to debug compose/pipe trace('label')(value)

If you want the full suite, just use the original Ramda.

Algebraic Typologies

// Source: https://github.com/DrBoolean
 
// Functor
Box.of(20).map(x => x / 2)
//Box(10)
 
// Monad
Box.of(true).chain(x => Box.of(!x))
// Box(false)
 
// Monoid
Box.of('small').concat(Box.of('pox'))
// Box('smallpox')
 
// Applicative
Box.of(x => x + 1).ap(2)
// Box(3)
 
// Traversable
Box.of(3).traverse(Either.x, x => fromNullable(x))
// Right(Box(3))
 
// Natural transformation
eitherToBox(fromNullable(null))
// Box(null)

When to use what? - Code that never fails!

const { Task, Either, Box, compose, map, fold, chain, ap, fork, trace } = require('ramda-x')
const fs = require('fs')
 
// we are using Task in order not to grab the state directly
// by doing so we isolate the side-effects and make our app more safely
 
// Use Task for side-effects: console.log, process.arg, http calls, db calls, read/write -> ASYNCHRONOUS CODE
const argv = Task((reject, resolve) => resolve(process.argv))
 
const httpGet = Task((reject, resolve) =>
    request(url, (err, res, body) =>
        err ? reject(err) : resolve(body)))
 
const readFile = enc => file => Task((reject, resolve) =>
    fs.readFile(file, enc, (err, content) =>
        err ? reject(err) : resolve(content)))
 
const file = readFile('utf-8')
file('config.json').fork(console.error, console.log)
 
 
// Use Eiter.try for JSON.parse/JSON.stringify -> SYNCHRONOUS CODE
const parse = Either.try(JSON.parse)
 
const readFileSync = Either.try(fs.readFileSync)
 
const result = readFileSync('config.json')
 
result.fold(console.error, console.log)
 
 
 
// Use Either.fromNullable when you are trying to get properties out of an object object.property
 
const first = ({ name }) =>
    Either.fromNullable(name)
 
 
const name = compose(
    chain(first),
    Either.of
)
 
const myName = name({ name: 'Dimitri' })
const herName = name({ name: 'Anastasia' })
 
myName.fold(console.error, console.log)
herName.fold(console.error, console.log)
 

Either.fromNullable - Code that never fails!

const { Task, Either, prop, compose, trace, map, fold, chain } = require('ramda-x')
 
const findColor = name =>
    ({ red: '#ff4444', green: '#36599', blue: '#fff68f' })[name]
 
const getColor = name => Either.fromNullable(findColor(name))
const sliceBy = str => str.slice(1)
const upperCaseValue = str => str.toUpperCase()
const reportError = err => 'no color'
const showResult = fold(reportError, upperCaseValue)
const sliceByTwo = map(sliceBy)
 
 
 
const result = compose(
    showResult,
    sliceByTwo,
    getColor)
 
 
result('green') // 36599
result('red') // FF4444
result('blue') // FFF68F
result('yellow') // no color
result('orange') // no color
result('white') // no color

ap && chain (Reigth/Left.chain || Right/Left.ap) - Code that never fails

const { Task, Either, prop, compose, trace, map, fold, chain } = require('ramda-x')
const fs = require('fs')
 
// Important: If you'll try to get a non-existing property out of the object, 
//the app would not return undefined it will return 3000 as default value of the error function, defined in showResult()
const getProperty = o =>
    Either.of(p => p).ap(Either.fromNullable(o.port))
 
const readFile = Either.try(fs.readFileSync)
const parseJSON = Either.try(JSON.parse)
const isPortAvailable = chain(getProperty)
const parse = chain(parseJSON)
const showResult = fold(
    err => 3000,
    c => c)
 
const result = compose(
    showResult,
    isPortAvailable,
    parse,
    readFile
)
 
 
result('config.json') // 8888
result('confffig.json') // 3000

Currying with Types (Boxes & Either)

const { Box } = require('ramda-x')
 
const add = x => y => x + y
const res = Box(add).ap(Box(20)).ap(Box(20)).fold(x => x)
 
 
console.log(
    res // 40
)
//---------------------------------- // ----------------------------------
const { Task, Either, prop, compose, trace, map, fold, chain, ap } = require('ramda-x')
 
const $ = selector =>
    Either.of({ selector, height: 10 })
 
 
const getScreenSize = screen => head => foot =>
    screen - (head.height + foot.height)
 
 
const result = compose(
    fold(err => 'error', x => x),
    ap($('hooter')),
    ap($('header')),
    Either.of,
    getScreenSize
)
 
console.log(
    result(800), // 780
    result(1500) // 1480
)

ap - safe and concurent IO operations - Code that never fails

const { Task, Either, prop, compose, trace, map, fold, chain, ap } = require('ramda-x')
const request = require('request')
 
const url1 = 'https://jsonplaceholder.typicode.com/posts/4'
const url2 = 'https://jsonplaceholder.typicode.com/posts/2'
 
const e = e => 'error'
const identity = x => x
const getTitle = o => Either.fromNullable(o.title).fold(e, identity)
const getId = o => Either.fromNullable(o.id).fold(e, identity)
 
const reportHeader = p1 => p2 =>
    `Report: ${p1.fold(e, body => getTitle(body))} compared to ${p2.fold(e, body => getTitle(body))}`
 
const reportId = p1 => p2 =>
    `Report: ${p1.fold(e, body => getId(body))} compared to ${p2.fold(e, body => getId(body))}`
 
const parse = Either.try(JSON.parse)
 
const httpGet = url =>
    Task((reject, resolve) =>
        request(url, (err, response, body) =>
            err ? reject(err) : resolve(parse(body)))
    )
 
const res = compose(
    ap(httpGet(url2)),
    ap(httpGet(url1)),
    Task.of
)
 
 
res(reportHeader).fork(err => 'error', data => console.log(data))
// Report: eum et est occaecati compared to qui est esse
 
res(reportId).fork(err => 'error', data => console.log(data))
// Report: 4 compared to 2
 
 

ap Redux - concurrent IO operations - Code that never fails

const { Task, Either, prop, compose, trace, map, fold, chain, ap } = require('ramda-x')
const request = require('request')
 
const update = msg => model =>
    msg.type === 'UPDATE'
        ? { ...model, report: msg.payload }
        : msg.type === 'ERROR'
            ? { ...model, error: msg.error }
            : model
 
const dispatch = msg => {
    let model = {}
    model = update(msg)(model)
    reportHeader(model)
    console.log('UPDATED MODEL', model)
}
 
const updateModelMsg = payload => ({ type: 'UPDATE', payload })
const updateModelErrorMsg = error => ({ type: 'ERROR', error })
 
 
const url1 = 'https://jsonplaceholder.typicode.com/posts/4'
const url2 = 'https://jsonplaceholder.typicode.com/posts/2'
 
const e = () => dispatch(updateModelErrorMsg('value not found'))
const identity = x => x
const getTitle = o => Either.fromNullable(o.title)
const getId = o => Either.fromNullable(o.id)
 
const reportHeader = p1 => p2 =>
    `Report: ${p1.chain(getTitle).fold(e, identity)} compared to ${p2.chain(getTitle).fold(e, identity)}`
 
const reportId = p1 => p2 =>
    `Report: ${p1.chain(getId).fold(e, identity)} compared to ${p2.chain(getId).fold(e, identity)}`
 
const parse = Either.try(JSON.parse)
 
const httpGet = url =>
    Task((reject, resolve) =>
        request(url, (err, response, body) =>
            err ? reject(err) : resolve(parse(body)))
    )
 
const res = compose(
    ap(httpGet(url2)),
    ap(httpGet(url1)),
    Task.of
)
 
 
res(reportHeader).fork(err => dispatch(updateModelErrorMsg(err)), data => dispatch(updateModelMsg(data)))
res(reportId).fork(err => 'error', data => dispatch(updateModelMsg(data)))
// UPDATED MODEL { report: 'Report: 4 compared to 2' }
// UPDATED MODEL { report: 'Report: eum et est occaecati compared to qui est esse' }

safe I/O Operations with Parsing - Code that never fails

const { Task, Either, prop, compose, trace, map, fold, chain, ap, fork } = require('ramda-x')
const fs = require('fs')
 
const readFile = enc => file =>
    Task((reject, resolve) =>
        fs.readFile(file, enc, (err, content) =>
            err ? reject(err) : resolve(content)
        )
    )
 
const writeFile = file => content =>
    Task((reject, resolve) =>
        fs.writeFile(file, content, (err, success) =>
            err ? reject(err) : resolve('success')
        )
    )
 
const writeToConfigTwo = writeFile('config2.json')
 
const parse = Either.try(JSON.parse)
const stringify = Either.try(JSON.stringify)
 
const getProperty = b =>
    Task.of(c => Either.fromNullable(c.port)).ap(b)
 
const eitherToTask = e =>
    e.fold(Task.rejected, Task.of)
 
const error = e => console.log('from error:', e)
const success = c => console.log('from success', c)
 
const transformation = compose(
    fork(error)(success),
    chain(writeToConfigTwo), // Task(Task(value)) -> Task(value) 
    chain(eitherToTask), // Task(Right(value)) -> Task(Task(value)) -> Task(value)
    map(stringify), // returns: Task(Right(value))
    chain(eitherToTask), // Task(Right(value)) -> Task(Task(value)) -> Task(value)
    getProperty, // returns:  Task(Right(value))
    chain(eitherToTask), // Task(Right(value)) -> Task(Task(value)) -> Task(value)
    map(parse), // returns: Task(Right(value))
    readFile('utf-8') // returns: Task(value)
)
 
transformation('configs.json')

Traversable with Lists

const { Task } = require('ramda-x')
const fs = require('fs')
 
 
const readFile = file =>
    Task((reject, resolve) =>
        fs.readFile(file, 'utf-8', (err, content) =>
            err ? reject(err) : resolve(content)))
 
 
const List = xs => ({
    concat: x => List(xs.concat(x)),
    map: fn => List(xs.map(fn)),
    reduce: (f, i) => List(xs.reduce(f, i)),
    fold: f => f(xs),
    traverse(of, fn) {
        return xs.reduce(
            (f, a) => fn(a).map(b => bs => bs.concat(b)).ap(f),
            of(List([]))
        )
    }
})
 
 
const files = List(['config.json', 'config2.json'])
 
 
files.traverse(Task.of, fn => readFile(fn)).fork(console.error, x => x.map(x => x + '!!!').fold(x => x))
``´
 
### Example
 
```js
const { map, prop, compose, trace } = require('ramda-x')
 
const users = [
    { name: 'Dimitri', isAdmin: true },
    { name: 'John', isAdmin: true },
    { name: 'Mike', isAdmin: false }
]
 
const names = map(prop('name'), users)
console.log(names) // [ 'Dimitri', 'John', 'Mike' ]
 
const message = {
    Records: [
        { name: 'Dimitri', isAdmin: true },
        { name: 'John', isAdmin: true },
        { name: 'Mike', isAdmin: false }
    ]
}
 
const extractNamesFromMessage = compose(
    map(prop('name')),
    trace('AFTER PLUCK'),
    prop('Records')
)
 
console.log(extractNamesFromMessage(message))
// AFTER PLUCK: [
//   {
//     "name": "Dimitri",
//     "isAdmin": true
//   },
//   {
//     "name": "John",
//     "isAdmin": true
//   },
//   {
//     "name": "Mike",
//     "isAdmin": false
//   }
// ]
//[ 'Dimitri', 'John', 'Mike' ]
 
const filterNamesFromMessage = compose(
    filter(propEq('name', 'Dimitri')),
    prop('Records')
)
 
console.log(filterNamesFromMessage(message))
// [ { name: 'Dimitri', isAdmin: true } ]

Task - Lazy Evaluation / Isolation of Side Effects

const {Task} = require('ramda-x')
 
// pure function
const readFile = (filename, enc) =>
    Task((reject, resolve) =>
        fs.readFile(filename, enc, (err, content) =>
            err ? reject(err) : resolve(content))
    )
// pure function
const writeFile = (filename, contents) =>
    Task((reject, resolve) =>
        fs.writeFile(filename, contents, (err, success) =>
            err ? reject(err) : resolve(success))
    )
 
// pure function
const app = readFile('config.json', 'utf-8')
    .map(content => content.replace(/8/g, '9'))
    .chain(contents => writeFile('config2.json', contents))
 
// impure function with side effects
app.fork(e => console.log('error', e), succes => console.log('success'))
 

Lifting a value into a type

const f = x => x.concat('!!!')
 
const res = Task.of('hello').map(f)
res.fork(e => 'error', d => console.log(d)) // hello!!!
 
Either.of('hello').map(f).fold(_ => _, d => console.log(d)) // hello!!!

Try/Catch Examples

const {Either} = require('ramda-x')
const fs = require('fs')
 
 
const tryCatch = f => {
    try {
        return Either.Right(f())
    } catch (e) {
        return Either.Left(e)
    }
}
 
const getPort = () =>
    tryCatch(() => fs.readFileSync('config.json'))
        .chain(c => tryCatch(() => JSON.parse(c)))
        .fold(e => 3000,
            c => c.port)
 
const res = getPort()

Try with Either.try(f)

const parse = Either.try(JSON.parse)
 
 
const eitherToTask = e =>
    e.fold(Task.rejected, Task.of)
 
const findPost = id =>
    httpGet(url(id))
        .map(parse)
        .chain(eitherToTask)
 
const main = ([id]) =>
    Task.of(title => [title]).ap(findPost(id))
 
id.chain(main).fork(console.error, x => console.log('success', x))
 

Either - Instead of If/Else + Composition

import {Either} = require('ramda-x')
 
const fromNullable = x =>
    x !== null ? Either.Right(x) : Either.Left(x)
 
// You can also import Either.fromNullable() and use it instead of fromNullable()
const findColor = name =>
    fromNullable({ red: '#ff4444', blue: '#3b5998', yellow: '#fffG8F' }[name])
 
 
const result = findColor('yellow').map(c => c.slice(1)).fold(err => 'nothing found', c => c.toUpperCase())

Either - Instead of If/Else + Composition

const { Task, Either, prop, compose, trace, map, fold, chain } = require('ramda-x')
const { fromNullable, Right, Left } = Either
 
const dispatch = x => console.log('action was dispatched', x)
const getItem = o => prop('item', o)
 
const someAction2 = dispatch => data =>
    compose(
        fold(e => 'comes from the err function', x => x),
        map(item => item),
        map(item => item + 2),
        chain(item => fromNullable(prop('item3', item))),
        fromNullable
    )(data)
 
const toUpperCase = str => str.toUpperCase()
 
const someAction3 = dispatch => data =>
    compose(
        toUpperCase,
        getItem
    )(data)
 
const prepeareAction = someAction2(dispatch)
const prepareNewAction = someAction3(dispatch)
 
prepeareAction(null) // comes from the err function -> the application runs without exiting
prepareNewAction(null) // TypeError: Cannot read property 'item' of null
 
 

Some other examples

const fromNullable = x =>
    x !== null ? Either.Right(x) : Either.Left(x)
 
const tryCatch = f => {
    try {
        return Either.Right(f())
    } catch (e) {
        return Either.Left(e)
    }
}
 
 
// imperative code
const openSite = () => {
    if (current_user) {
        return renderPage(current_user)
    } else {
        return showLogin()
    }
}
 
 
// declarative code
const openSite = () => {
    fromNullable(current_user)
        .fold(showLogin, renderPage)
}
 
// imperative code
const getPrefs = user => {
    if (user.premium) {
        return loadPrefs(user.preferences)
    } else {
        return defaultPrefs
    }
}
 
// declarative code
const getPrefs = user =>
    (user.premium ? Right(user) : Left('not premium'))
        .map(u => u.preferences)
        .fold(() => defaultPrefs, prefs => loadPrefs(prefs))
 
// imperative code
const streetName = user => {
    const address = user.address
    if (address) {
        const street = address.street
        if (street) {
            return street.name
        }
    }
    return 'no street!'
}
 
// declarative code
const streetName = user =>
    fromNullable(user.address)
        .chain(a => fromNullable(a.street))
        .map(s => s.name)
        .fold(e => 'no street', n => n)
 
// imperative code
const concatUniq = (x, ys) => {
    const found = ys.filter(y => y === x)[0]
    return found ? ys : ys.concat(x)
}
 
// declarative code
const concatUniq = (x, ys) =>
    fromNullable(ys.filter(y => y === x)[0])
        .fold(() => ys.concat(x), y => ys)
 
// imperative code
const wrapExamples = example => {
    if (example.previewPath) {
        try {
            example.preview = fs.readFileSync(example.previewPath)
        } catch (e) { }
    }
    return example
}
 
const readFile = x => tryCatch(() => fs.readFileSync(x))
 
// declarative code
const wrapExamples = example => {
    fromNullable(example.previewPath)
        .chain(readFile)
        .fold(() => example, ex => Object.assign({ preview: p }, ex))
}
 
// imperative code
const parseDbUrl = cfg => {
    try {
        const c = JSON.parse(cfg)
        if(c.url) {
            return c.url.match(/*....*/)
        }
    } catch(e) {
        return null
    }
}
 
// declarative code
const parseDbUrl = cfg => {
    tryCatch(() => JSON.parse(cfg))
    .chain(c => fromNullable(c.url))
    .fold(e => null,
        u => u.match(/*...*/))
}

Dependents (0)

Package Sidebar

Install

npm i ramda-x

Weekly Downloads

1

Version

1.0.11

License

MIT

Unpacked Size

24.8 kB

Total Files

8

Last publish

Collaborators

  • tarasowski