1

I'm trying to create settings module with interface that can give functions for changing and reading from settings object.

Now code looks like this:

let _settings = {
    user: {
        id: userId || '',
        authorized: false,
    }
}

function getSettings() {
    return _settings
}

function addParamToSettings(param) {
    _settings = {
        ..._settings,
        ...param,
    }
}

function addParamToUser(param) {
    addParamToSettings({
        user: {
            ...getSettings().user,
            ...param,
        },
    })
}

let saveUserId = function saveUserId(id) {
    addParamToUser({ id, authorized: true })
}

I want to rewrite this in more functional way (like tacit style, using lenses, immutable and so on), better using Ramda js

First of all I don't understand how to work in functional way then you need some kind of information storage (like object _settings in this example) that you can read and write into it.

I've tried to rewrite addParamToApp function, and the best shot was:

const userLense = R.lensProp('user')

const _addParamToUser = R.compose(R.set(userLense, R.__, _settings), R.merge(_settings.user))

But i still need to write function for handle _settings change

const addParamToUser = function addParamToUser(param){
    _settings = _addParamToUser(param)
}

That doesn't seems to me right. Also that much more code then in first realization.

How can i write it in more functional way? How to handle information storage with read and write functions?

Dionid
  • 97
  • 8
  • 2
    Don't add functional features just to tick off boxes on the functional checklist. *Why* do you feel you need to change the current code? Too hard to test/reason about? Only change I'd make is that you have functions that do nothing other than call other functions with some preset parameters. Use `.apply` and/or `compose` instead. – Jared Smith Mar 25 '17 at 14:19
  • Your code is just trying to overwrite _settings variable with `id` and `authorized`, is it too simplified? why not just R.set? – AZ. Mar 25 '17 at 17:23
  • @JaredSmith I couldn't have said that better myself – Mulan Mar 25 '17 at 21:32
  • @naomik thank you. I love FP, but sometimes feel that recent FP converts are like this guy https://circleci.com/blog/its-the-future/ – Jared Smith Mar 26 '17 at 01:49

1 Answers1

4

The State Monad

You might be interested in exploring the State monad. I'll go through a couple parts of the program first and then include a complete runnable example at the bottom.

First, we'll cover the State monad. There are countless monad introductions online which are out of scope for this post, so I'm just going to cover just enough to get you up and running. Other implementations of State monad will have additional conveniences; be sure to learn about those if you're interested in learning more.

// State monad
const State = runState => ({
  runState,
  bind: f => State(s => {
    let { value, state } = runState(s)
    return f(value).runState(state)
  }),
  evalState: s =>
    runState(s).value,
  execState: s =>
    runState(s).state
})

State.pure = y =>
  State(x => ({ value: y, state: x }))

State.get = () =>
  State(x => ({ value: x, state: x }))

State.put = x =>
  State($ => ({ value: null, state: x }))

Rules first

As with all monads, to be a monad, it must satisfy the following three monad laws.

  1. left identity: pure(a).bind(f) == f(a)
  2. right identity: m.bind(pure) == m
  3. associativity: m.bind(f).bind(g) == m.bind(x => f(x).bind(g))

Where pure is our way of putting a value into an instance of our Monad, m, and bind is our way of interacting with the instance's contained value – You'll sometimes here pure called return in other descriptions of monads, but I'll avoid using return in this answer as it's a keyword in JavaScript and not related to monads.

Don't worry too much about understanding the implementation details. When you're starting out, it's better to develop an intuition about how the State monad works. We'll see that in a moment as we now look at a sample program using the state monad


Your first stateful function

Assuming your initial program state is {}, we'll look at a function that modifies the state to add the user and corresponding userId. This could be the result of looking up a user in the database and we set the authorized property to false until we later verify that the user's password is correct

const setUser = userId =>
  State.get().bind(settings =>
    State.put(Object.assign({}, settings, {
      user: { userId, authorized: false }
    })))

Step 1 of this function is to get the state using State.get() – on its own, it seems like it does nothing, but it's in the context where this function is called that it makes a distinct difference. Later on you'll see where we execute the computation with an initial state value which State.get seemingly pulls out of thin air. Again, for now, just get an intuition for it and assume that somehow we get the state.

Step 2 of this is the bind(settings => ... bit. Recall bind is how we interact with our state value, and in this case it's our settings object. So all we really want to do here is update settings to include a user property set to { userId, authorized: false }. After setUser is called, we can think of our state having a new shape of

// old state
let settings = {...}

// new state
settings = { ...settings, { user: { userId, authorized: false } } }

A second stateful function

Ok, so your user wants to login now. Let's accept a challenge and compare it against some password – if the user provided the correct password, we'll update the state to show authorized: true, otherwise we'll just leave it set to false

const PASSWORD = 'password1'

const attemptLogin = challenge =>
  State.get().bind(settings => {
    let { userId, authorized } = settings.user
    if (challenge === PASSWORD)
      return State.put(Object.assign({}, settings, {
        user: { userId, authorized: true }
      }))
    else
      return State.pure(settings)
  })

Step 1 is to get the state again, just like last time

Step 2 is bind to get access to the state value and do something. Recall where we left off in the previous state modification (new state at the end of the last section): to read the user's current state, we want to read settings.user.

Step 3 is to decide what the next state will be: if the user provided the correct challenge (ie, it's equal to PASSWORD), the we will return a new state with authorized set to true – otherwise, if the challenge does not match the password, return an unmodified state using State.pure(settings)


Your first stateful program

So now we have two functions (setUser and attemptLogin) that read state and return state of their own. Writing our program is easy now

const initialState = {}

const main = (userId, challenge) => 
  setUser(userId)
    .bind(() => attemptLogin(challenge))
    .execState(initialState)

That's it. Run the complete code example below to see output for two login scenarios: one with a valid password, and one with an invalid password


Complete code example

// State monad
const State = runState => ({
  runState,
  bind: f => State(s => {
    let { value, state } = runState(s)
    return f(value).runState(state)
  }),
  evalState: s =>
    runState(s).value,
  execState: s =>
    runState(s).state
})

State.pure = y =>
  State(x => ({ value: y, state: x }))

State.get = () =>
  State(x => ({ value: x, state: x }))

State.put = x =>
  State($ => ({ value: null, state: x }))

// your program
const PASSWORD = 'password1'
const initialState = {}

const setUser = userId =>
  State.get().bind(settings =>
    State.put(Object.assign({}, settings, {
      user: { userId, authorized: false }
    })))
    
const attemptLogin = challenge =>
  State.get().bind(settings => {
    let { userId, authorized } = settings.user
    if (challenge === PASSWORD)
      return State.put(Object.assign({}, settings, {
        user: { userId, authorized: true }
      }))
    else
      return State.pure(settings)
  })
  
const main = (userId, challenge) => 
  setUser(userId)
   .bind(() => attemptLogin(challenge))
   .execState(initialState)

// good login
console.log(main(5, 'password1'))
// { user: { userId: 5, authorized: true } }

// bad login
console.log(main(5, '1234'))
// { user: { userId: 5, authorized: false } }

Where to go from here?

This just scratches the surface on the state monad. It took me a long time to gain some intuition on how it works, and I still am yet to master it.

If you're scratching your head on how it works, I would strongly encourage you to do the old pen/paper evaluation strategy – trace the program and be meticulous with your substitutions. It's amazing when you see it all come together.

And be sure to do some more reading on State monad at various sources


"better using Ramda?"

I think part of your problem is that you're lacking some of the fundamentals to reason about programs in a functional way. Reaching for a library like Ramda is not likely to help you develop the skill or give you a better intuition. In my own experience, I learn best by sticking to the basics and building up from there.

As a discipline, you could practice implementing any Ramda function before using it. Only then can you truly know/appreciate what Ramda is bringing into the mix.

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • @Bergi I felt a little wobbly by the end of writing this one. It was hard to walk the line of showing just enough to get some output without implementing so much that the reader would get suffocated with details – I'm especially uncomfortable renaming `return` to `pure` to avoid the `return` keyword in JavaScript. If you have any pointers on improving this answer, I'd greatly appreciate it ^_^ – Mulan Mar 25 '17 at 21:34
  • Great write up! It might be helpful to use (or include) fantasy land's naming conventions. So `pure` becomes `of` and `bind` becomes `chain`. Particularly if the OP is interested in the ramda ecosystem. – James Forbes Mar 26 '17 at 11:50
  • Thank a lot for this answer! But still, i don't understand how can i get a result from main function later in my code, for example after user made some actions? – Dionid Apr 01 '17 at 15:18
  • @Dionid yeah you have a complex problem and you're going to need to understand quite a bit before you're ready to solve it using functional programming techniques. If you're interested, you'll want to look at the `Future` monad and perhaps `StateT` monad transformer. I'm still learning how to express async programs in a functional way, so I'm not the best person to take you further. I might also suggest a lateral approach using something like `redux` which mutates state but in a very systematic way. – Mulan Apr 01 '17 at 17:13
  • @naomik I've already pretty familiar with Redux and i've already created this platform by using it (it works really well with code without ui, but need a little more interface to imitate mapStateToProps and subscribe modules on state changes). – Dionid Apr 02 '17 at 10:35