我正在尝试创建具有接口的设置模块,该接口可以提供用于更改和读取设置对象的功能。
现在代码看起来像这样:
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)
返回未修改的状态您的第一个有状态程序
因此,现在我们有了两个函数(
setUser
和attemptLogin
),它们分别读取状态和返回状态。现在编写我们的程序很容易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/