我正在尝试使用界面创建设置模块,可以为更改和从设置对象中读取功能。
现在代码如下所示:
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)
}
这在我看来并不合适。在第一次实现中还有更多的代码。
我怎样才能以更实用的方式编写它?如何使用读写功能处理信息存储?
答案 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定律。
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
在其他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
您的第一个有状态计划
所以现在我们有两个函数(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 } }
那就是它。运行下面的完整代码示例以查看两个登录方案的输出:一个具有有效密码,另一个具有无效密码
完整的代码示例
{{1}}
从哪里开始?
这只是在状态monad上划伤了表面。我花了很长时间才对它的工作方式有了一些直觉,我还没有掌握它。
如果您对它的工作方式有所了解,我强烈建议您执行旧的笔/纸评估策略 - 跟踪程序并对您的替换进行细致处理。当你看到它们聚集在一起时,它真是太神奇了。
并且一定要在各种来源上更多地阅读State monad
"更好地使用Ramda?"
我认为你的部分问题在于你缺乏一些基本功能,无法以功能的方式推理程序。到达像Ramda这样的图书馆不太可能帮助你发展技能或给你一个更好的直觉。根据我自己的经验,我通过坚持基础知识和从那里积累来学习。
作为一门学科,你可以在使用之前练习实现任何Ramda功能。只有这样,你才能真正了解/欣赏Ramda带来的东西。