如何将这个javascript代码转换为功能方式(更好地使用Ramdajs)?

时间:2017-03-25 13:07:31

标签: javascript functional-programming ramda.js

我正在尝试使用界面创建设置模块,可以为更改和从设置对象中读取功能。

现在代码如下所示:

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 })
}

我想以更多功能的方式重写它(如默认风格,使用镜头,不可变等),更好地使用Ramda js

首先,我不明白如何以功能方式工作,然后您需要某种信息存储(如本例中的对象 _settings ),您可以读取和写入它。

我试图重写 addParamToApp 功能,最好的镜头是:

const userLense = R.lensProp('user')

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

但是我仍然需要为句柄 _settings 更改

编写函数
const addParamToUser = function addParamToUser(param){
    _settings = _addParamToUser(param)
}

这在我看来并不合适。在第一次实现中还有更多的代码。

我怎样才能以更实用的方式编写它?如何使用读写功能处理信息存储?

1 个答案:

答案 0 :(得分:4)

State Monad

您可能有兴趣探索州立大学。我将首先完成程序的几个部分,然后在底部包含一个完整的可运行示例。

首先,我们将覆盖州立大学。网上有无数的monad介绍超出了这篇文章的范围,所以我只是要覆盖足够的内容来启动和运行。 State monad的其他实现将有额外的便利;如果您有兴趣了解更多内容,请务必了解这些内容。

// 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 }))

优先规则

与所有monad一样,要成为monad,它必须满足以下三个monad定律。

  1. 左侧身份pure(a).bind(f) == f(a)
  2. 正确的身份m.bind(pure) == m
  3. 关联性m.bind(f).bind(g) == m.bind(x => f(x).bind(g))
  4. pure是我们将值添加到Monad实例中的方式,mbind是我们与实例包含的值进行交互的方式 - 您有时会pure在其他monad描述中return调用return,但我会避免在此答案中使用{},因为它是JavaScript中的关键字与monads无关。

    不要过分担心理解实施细节。当你开始时,最好是建立一个关于如何状态monad工作的直觉。我们现在可以看到使用状态monad的示例程序

    您的第一个有状态功能

    假设您的初始计划状态为user,我们会查看修改状态的函数,以添加userId和相应的authorized。这可能是在数据库中查找用户的结果,我们将false属性设置为const setUser = userId => State.get().bind(settings => State.put(Object.assign({}, settings, { user: { userId, authorized: false } }))) ,直到我们稍后验证用户的密码是否正确

    State.get()

    这个函数的第一步是使用State.get来获取状态 - 它本身似乎什么也没做,但它在调用这个函数的上下文中它是一个独特的区别。稍后你会看到我们用初始状态值执行计算的地方bind(settings => ...似乎已经凭空消失了。再说一遍,现在,只是对它有一个直觉,并假设以某种方式我们得到了状态。

    这是bind位的第2步。召回settings是我们与州价值互动的方式,在这种情况下,它是我们的settings对象。因此,我们真正想要做的就是更新user以将{ userId, authorized: false }属性设置为setUser。调用// old state let settings = {...} // new state settings = { ...settings, { user: { userId, authorized: 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) })

    bind

    步骤1是再次获得状态,就像上次一样

    第2步是new state,可以访问状态值,执行。回想一下我们在之前的状态修改中停止的地方(最后一部分末尾的settings.user):要阅读用户的当前状态,我们想要阅读challenge

    步骤3是决定下一个状态:如果用户提供了正确的PASSWORD(即,它等于authorized),我们将返回一个新状态将true设置为State.pure(settings) - 否则,如果质询与密码不匹配,请使用setUser

    返回未修改的状态

    您的第一个有状态计划

    所以现在我们有两个函数(attemptLoginconst initialState = {} const main = (userId, challenge) => setUser(userId) .bind(() => attemptLogin(challenge)) .execState(initialState) )来读取它们自己的状态和返回状态。现在编写我们的程序很容易

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

    那就是它。运行下面的完整代码示例以查看两个登录方案的输出:一个具有有效密码,另一个具有无效密码

    完整的代码示例

    
    
    {{1}}
    
    
    

    从哪里开始?

    这只是在状态monad上划伤了表面。我花了很长时间才对它的工作方式有了一些直觉,我还没有掌握它。

    如果您对它的工作方式有所了解,我强烈建议您执行旧的笔/纸评估策略 - 跟踪程序并对您的替换进行细致处理。当你看到它们聚集在一起时,它真是太神奇了。

    并且一定要在各种来源上更多地阅读State monad

    "更好地使用Ramda?"

    我认为你的部分问题在于你缺乏一些基本功能,无法以功能的方式推理程序。到达像Ramda这样的图书馆不太可能帮助你发展技能或给你一个更好的直觉。根据我自己的经验,我通过坚持基础知识和从那里积累来学习。

    作为一门学科,你可以在使用之前练习实现任何Ramda功能。只有这样,你才能真正了解/欣赏Ramda带来的东西。