简体   繁体   English

如何将此JavaScript代码转换为功能方式(最好使用Ramdajs)?

[英]How to translate this javascript code into functional way (better using Ramdajs)?

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 我想以更实用的方式(例如默认样式,使用镜头,不可变等)重写此代码,更好地使用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. 首先,我不了解如何以功能方式工作,然后您需要某种可以读取和写入其中的信息存储(例如本例中的对象_settings )。

I've tried to rewrite addParamToApp function, and the best shot was: 我试图重写addParamToApp函数,最好的镜头是:

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 但是我仍然需要编写函数来处理_settings更改

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? 如何使用读写功能处理信息存储?

The State Monad 国家莫纳德

You might be interested in exploring the State monad. 您可能有兴趣探索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. 首先,我们将介绍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. 网上有不计其数的monad介绍,这些内容超出了本文的讨论范围,因此,我将只介绍足够的内容,以帮助您入门和运行。 Other implementations of State monad will have additional conveniences; State monad的其他实现将有更多便利; 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) 左身份pure(a).bind(f) == f(a)
  2. right identity : m.bind(pure) == m 右身份m.bind(pure) == m
  3. associativity : m.bind(f).bind(g) == m.bind(x => f(x).bind(g)) 关联性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. pure是我们把一个值的一个实例的方式我们的单子, m ,并且bind是我们与实例的包含价值互动的方式-有时你会在这里purereturn在单子的其他描述,但我会避免在此答案中使用return ,因为它是JavaScript中的关键字,与monad无关。

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 稍后,我们将看到使用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 . 假设您的初始程序状态为{} ,我们将看一个修改状态以添加user和对应的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 这可能是由于在数据库中查找用户而导致的,我们将authorized属性设置为false直到稍后我们验证用户密码是否正确

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. 该函数的第一步是使用State.get()来获取状态-看起来它什么都不做,但是在上下文中调用此函数会产生明显的不同。 Later on you'll see where we execute the computation with an initial state value which State.get seemingly pulls out of thin air. 稍后,您将看到我们在哪里执行带有初始状态值的计算,该状态值看起来State.get抽出来的。 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 第2步是bind(settings => ...位。调用bind是我们与状态值进行交互的方式,在这种情况下,这是我们的settings对象。因此,我们真正要做的就是更新settings以包含一个user属性设置为{ userId, authorized: false }setUser之后,我们可以认为状态为

// 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 让我们接受一个challenge并将其与某些密码进行比较–如果用户提供了正确的密码,我们将更新状态以显示authorized: true ,否则我们将其设置为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 步骤1是重新获取状态,就像上一次一样

Step 2 is bind to get access to the state value and do something. bind步骤2,以访问状态值并执行某些操作。 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 . 回想一下我们在上一个状态修改(上一节末尾的new state中保留的地方:要读取用户的当前状态,我们要读取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) 步骤3决定下一个状态:如果用户提供了正确的challenge (即等于PASSWORD ),我们将返回authorized状态设置为true的新状态–否则,如果质询与密码,使用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. 所以现在我们有两个函数( setUserattemptLogin ),它们分别读取状态和返回状态。 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. 这只是在状态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. 如果您对工作原理scratch之以鼻,我强烈建议您采用旧的笔/纸评估策略-跟踪程序并谨慎选择替换方案。 It's amazing when you see it all come together. 当您看到它们融合在一起时,真是太神奇了。

And be sure to do some more reading on State monad at various sources 并确保通过各种来源对State monad进行更多阅读


"better using Ramda?" “最好使用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. 到达Ramda之类的图书馆不太可能帮助您发展技能或提供更好的直觉。 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. 作为一门学科,您可以练习使用任何Ramda函数,然后再使用它。 Only then can you truly know/appreciate what Ramda is bringing into the mix. 只有这样,您才能真正知道/欣赏Ramda带来的收益。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM