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(/*...*/))
    }

    Install

    npm i ramda-x

    DownloadsWeekly Downloads

    0

    Version

    1.0.11

    License

    MIT

    Unpacked Size

    24.8 kB

    Total Files

    8

    Last publish

    Collaborators

    • tarasowski