簡體   English   中英

如何將此JavaScript代碼轉換為功能方式(最好使用Ramdajs)?

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

我正在嘗試創建具有接口的 設置模塊 ,該接口可以提供用於更改和讀取設置對象的功能。

現在代碼看起來像這樣:

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

規則優先

與所有單子一樣,要成為單子,它必須滿足以下三個單子法。

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

pure是我們把一個值的一個實例的方式我們的單子, m ,並且bind是我們與實例的包含價值互動的方式-有時你會在這里purereturn在單子的其他描述,但我會避免在此答案中使用return ,因為它是JavaScript中的關鍵字,與monad無關。

不必太擔心了解實現細節。 當你開始,這是更好地制定有關國家單子是如何工作的直覺。 稍后,我們將看到使用state 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抽出來的。 再次,就目前而言,僅憑直覺就可以假設我們以某種方式獲得了狀態。

第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是重新獲取狀態,就像上一次一樣

bind步驟2,以訪問狀態值並執行某些操作。 回想一下我們在上一個狀態修改(上一節末尾的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進行更多閱讀


“最好使用Ramda嗎?”

我認為您的問題的一部分是,您缺乏以功能性方式推理程序的一些基礎知識。 到達Ramda之類的圖書館不太可能幫助您發展技能或提供更好的直覺。 以我自己的經驗,我會通過堅持基礎知識並從那里建立基礎來學習最好。

作為一門學科,您可以練習使用任何Ramda函數,然后再使用它。 只有這樣,您才能真正知道/欣賞Ramda帶來的收益。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM