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

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


在我看来,这似乎不对。还有更多的代码,然后第一次实现。

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

最佳答案

国家莫纳德

您可能有兴趣探索State monad。我将首先浏览程序的两个部分,然后在底部包括一个完整的可运行示例。

首先,我们将介绍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 }))




规则优先

与所有单子一样,要成为单子,它必须满足以下三个单子法。


左身份:pure(a).bind(f) == f(a)
正确的身份:m.bind(pure) == m
关联性:m.bind(f).bind(g) == m.bind(x => f(x).bind(g))


其中pure是我们将值放入Monad实例的方式,m,而bind是我们与实例包含的值进行交互的方式–您有时会在此处将pure称为return其他有关monad的描述,但我将避免在此答案中使用return,因为它是JavaScript中的关键字,与monad无关。

不必太担心了解实现细节。当您刚开始时,最好对State monad的工作方式有一个直观认识。稍后,我们将看到使用state monad的示例程序



您的第一个有状态功能

假设您的初始程序状态为{},我们将看一个修改状态以添加user和相应的userId的函数。这可能是在数据库中查找用户的结果,并且我们将authorized属性设置为false,直到我们稍后验证用户密码正确无误为止

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


该功能的第1步是使用State.get()来获取状态-似乎它什么也不做,但是在调用此功能的上下文中,它产生了明显的不同。稍后,您将看到我们在哪里使用初始状态值执行计算,而State.get似乎无足轻重。再次,就目前而言,仅凭直觉就可以假设我们以某种方式获得了状态。

步骤2是bind(settings => ...位。回想一下bind是我们如何与状态值进行交互,在这种情况下,它就是我们的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)
  })


步骤1是重新获取状态,就像上一次一样

步骤2是bind,可以访问状态值并执行某些操作。回想一下我们在上一状态修改(上一节末尾的new state)中保留的地方:要读取用户的当前状态,我们要读取settings.user

步骤3是决定下一个状态是什么:如果用户提供了正确的challenge(即等于PASSWORD),则我们将返回一个新的状态,将authorized设置为true –否则,如果挑战与密码不匹配,请使用State.pure(settings)返回未修改的状态



您的第一个有状态程序

因此,现在我们有了两个函数(setUserattemptLogin),它们分别读取状态和返回状态。现在编写我们的程序很容易

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







然后去哪儿?

这只是在状态monad上刮擦了表面。我花了很长时间了解它的工作原理,但我仍然没有掌握它。

如果您对工作原理scratch之以鼻,我强烈建议您采用旧的笔/纸评估策略-跟踪程序并谨慎选择替换方案。当您看到它们融合在一起时,真是太神奇了。

并确保通过各种来源对State monad进行更多阅读


wikipedia.org:Monad (functional Programming) - State Monad
haskell.org:State Monad
learnyouahaskell.com:For a few Monads More - Tasteful Stateful Computations
还有其他你能找到的




“最好使用Ramda吗?”

我认为您的问题的一部分是,您缺乏以功能性方式推理程序的一些基础知识。到达Ramda之类的图书馆不太可能帮助您发展技能或提供更好的直觉。以我自己的经验,我会通过坚持基础知识并从那里建立基础来学习最好。

作为一门学科,您可以练习使用任何Ramda函数,然后再使用它。只有这样,您才能真正知道/欣赏Ramda带来的收益。

关于javascript - 如何将此JavaScript代码转换为功能方式(最好使用Ramdajs)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43016716/

相关文章:

javascript - 使用 jQuery 的内联元素的左偏移量

c++ - tr1::mem_fn 和 tr1::bind:常量正确性和重载

javascript - 使用 JavaScript .map() 和 .filter() 按 lang 搜索作者/书籍数据模块

javascript - 多个切换隐藏div而不取消隐藏已经隐藏的

javascript - sails.js 在创建之前修改模型

javascript - 从另一个单元格内动态创建的元素获取第一个单元格的内容

functional-programming - 惰性求值在这个例子中是如何工作的?

javascript - 有没有类似 lodash _.toArray for ramda.js 的东西?

javascript - lambda : get objects from array by comparing with each item in another array

javascript - 是否有将 SqlResultsetRowList 转换为数组的首选方法?