The typical Redux Reducer is function that takes in the previous state and an action and uses a switch case to determine how to transition the provided State. We can take advantage of the Action Names being Strings and replace the typical switch case with a JavaScript Object that pairs our State ADT reducers with their actions.

We can also take care of the case in which a reducer does not have an implementation for a given action, by reaching for the Maybe data type and updating our main Redux reducer to handle the case for us, by providing the previous state untouched. And all of this can be captured in a simple helper function we can use in all of our reducer files.

This post will focus on how to do reducer pattern, not the functionalities details, all the functionalities codes are listed here:

const {prop, isSameType, State, when, assign, omit, curry, converge,map, composeK, liftA2, equals, constant,option, chain, mapProps, find, propEq, isNumber, compose, safe} = require('crocks');
const {get, modify, of} = State; const state = {
cards: [
{id: 'green-square', color: 'green', shape: 'square'},
{id: 'orange-square', color: 'orange', shape: 'square'},
{id: 'blue-triangle', color: 'blue', shape: 'triangle'}
],
hint: {
color: 'green',
shape: 'square'
},
isCorrect: null,
rank: ,
left: ,
moves:
}
const inc = x => x + ;
const dec = x => x - ;
const incOrDec = b => b ? dec : inc;
const clamp = (min, max) => x => Math.min(Math.max(min, x), max);
const clampAfter = curry((min, max, fn) => compose(clamp(min, max), fn))
const limitRank = clampAfter(, );
const over = (key, fn) => modify(mapProps({[key]: fn}))
const getState = key => get(prop(key));
const liftState = fn => compose(
of,
fn
)
const limitMoves = clampAfter(, );
const decLeft = () => over("left", limitMoves(dec));
const incMoves = () => over("moves", limitMoves(inc));
const assignBy = (pred, obj) =>
when(pred, assign(obj));
const applyMove =
composeK(decLeft, incMoves) const getCard = id => getState('cards')
.map(chain(find(propEq('id', id))))
.map(option({}))
const getHint = () => getState('hint')
.map(option({}))
const cardToHint = composeK(
liftState(omit(['id'])),
getCard
)
const validateAnswer = converge(
liftA2(equals),
cardToHint,
getHint
)
const setIsCorrect = b => over('isCorrect', constant(b));
const adjustRank = compose(limitRank, incOrDec);
const updateRank = b => over('rank', adjustRank(b));
const applyFeedback = converge(
liftA2(constant),
setIsCorrect,
updateRank
)
const markSelected = id =>
assignBy(propEq('id', id), { selected: true })
const selectCard = id =>
over('cards', map(markSelected(id)))
const answer = composeK(
applyMove,
selectCard
)
const feedback = composeK(
applyFeedback,
validateAnswer
)

For now, the reducer pattern was implemented like this:

// Action a :: {type: string, payload: a}
// createAction :: String -> a -> Action a
const createAction = type => payload => ({type, payload});
const SELECT_CARD = 'SELECT_CARD';
const SHOW_FEEDBACK = 'SHOW_FEEDBACK';
const selectCardAction = createAction(SELECT_CARD);
const showFeedbackAction = createAction(SHOW_FEEDBACK); // reducer :: (State, a) -> (State AppState ()) | Null
const reducer = (prevState, {type, payload}) => {
let result;
switch(type) {
case SELECT_CARD:
result = answer(payload);
break;
case SHOW_FEEDBACK:
result = feedback(payload);
break;
default:
result = null;
} return isSameType(State, result) ? result.execWith(prevState): prevState;
} console.log(
reducer(
state,
showFeedbackAction('green-square')
)
)

Instead of using 'Switch', we can use Object to do the reducer:

const actionReducer= {
SELECT_CARD: answer,
SHOW_FEEDBACK: feedback
} const turn = ({type, payload}) => (actionReducer[type] || Function.prototype)(payload); const reducer = (prevState, action) => {
const result = turn(action);
return isSameType(State, result) ? result.execWith(prevState) : prevState;
}

'Function.prototype' makes sure that it always return a function can accept payload as param, just it do nothing and return undefined.

And code:

(actionReducer[type] || Function.prototype)

it is prefect for Maybe type, so we can continue with refactoring with Maybe:

const createReducer = actionReducer => ({type, payload}) =>
prop(type, actionReducer) // safe check type exists on actionReducer
.map(applyTo(payload)) // we get back a function need to call with payload, using applyTo
const turn = createReducer({
SELECT_CARD: answer,
SHOW_FEEDBACK: feedback
})
const reducer = (prevState, action) => {
return turn(action)
.chain(safe(isSameType(State))) // check result is the same type as State
.map(execWith(prevState)) // run with execWith
.option(prevState); // unwrap Just and provide default value };

---

 const {prop, execWith, applyTo, isSameType, State, when, assign, omit, curry, converge,map, composeK, liftA2, equals, constant,option, chain, mapProps, find, propEq, isNumber, compose, safe} = require('crocks');
const {get, modify, of} = State; const state = {
cards: [
{id: 'green-square', color: 'green', shape: 'square'},
{id: 'orange-square', color: 'orange', shape: 'square'},
{id: 'blue-triangle', color: 'blue', shape: 'triangle'}
],
hint: {
color: 'green',
shape: 'square'
},
isCorrect: null,
rank: ,
left: ,
moves:
}
const inc = x => x + ;
const dec = x => x - ;
const incOrDec = b => b ? dec : inc;
const clamp = (min, max) => x => Math.min(Math.max(min, x), max);
const clampAfter = curry((min, max, fn) => compose(clamp(min, max), fn))
const limitRank = clampAfter(, );
const over = (key, fn) => modify(mapProps({[key]: fn}))
const getState = key => get(prop(key));
const liftState = fn => compose(
of,
fn
)
const limitMoves = clampAfter(, );
const decLeft = () => over("left", limitMoves(dec));
const incMoves = () => over("moves", limitMoves(inc));
const assignBy = (pred, obj) =>
when(pred, assign(obj));
const applyMove =
composeK(decLeft, incMoves) const getCard = id => getState('cards')
.map(chain(find(propEq('id', id))))
.map(option({}))
const getHint = () => getState('hint')
.map(option({}))
const cardToHint = composeK(
liftState(omit(['id'])),
getCard
)
const validateAnswer = converge(
liftA2(equals),
cardToHint,
getHint
)
const setIsCorrect = b => over('isCorrect', constant(b));
const adjustRank = compose(limitRank, incOrDec);
const updateRank = b => over('rank', adjustRank(b));
const applyFeedback = converge(
liftA2(constant),
setIsCorrect,
updateRank
)
const markSelected = id =>
assignBy(propEq('id', id), { selected: true })
const selectCard = id =>
over('cards', map(markSelected(id)))
const answer = composeK(
applyMove,
selectCard
)
const feedback = composeK(
applyFeedback,
validateAnswer
)
// Action a :: {type: string, payload: a}
// createAction :: String -> a -> Action a
const createAction = type => payload => ({type, payload});
const SELECT_CARD = 'SELECT_CARD';
const SHOW_FEEDBACK = 'SHOW_FEEDBACK';
const selectCardAction = createAction(SELECT_CARD);
const showFeedbackAction = createAction(SHOW_FEEDBACK); const createReducer = actionReducer => ({type, payload}) =>
prop(type, actionReducer) // safe check type exists on actionReducer
.map(applyTo(payload)) // we get back a function need to call with payload, using applyTo
const turn = createReducer({
SELECT_CARD: answer,
SHOW_FEEDBACK: feedback
}) const reducer = (prevState, action) => {
return turn(action)
.chain(safe(isSameType(State))) // check result is the same type as State
.map(execWith(prevState)) // run with execWith
.option(prevState); // unwrap Just and provide default value }; const sillyVerb = createAction('SILLY_VERB'); console.log(
reducer(
state,
selectCardAction('green-square')
)
)

[Functional Programming ADT] Create State ADT Based Reducers (applyTo, Maybe)的更多相关文章

  1. [Functional Programming] Combine Multiple State ADT Instances with the Same Input (converge(liftA2(constant)))

    When combining multiple State ADT instances that depend on the same input, using chain can become qu ...

  2. [Functional Programming] Compose Simple State ADT Transitions into One Complex Transaction

    State is a lazy datatype and as such we can combine many simple transitions into one very complex on ...

  3. [React + Functional Programming ADT] Connect State ADT Based Redux Actions to a React Application

    With our Redux implementation lousy with State ADT based reducers, it is time to hook it all up to a ...

  4. [Functional Programming] Define Discrete State Transitions using the State ADT

    We build our first state transactions as two discrete transactions, each working on a specific porti ...

  5. [Functional Programming] Reader with Async ADT

    ReaderT is a Monad Transformer that wraps a given Monad with a Reader. This allows the interface of ...

  6. [Functional Programming] Introduction to State, thinking in State

    Recently, I am learning Working with ADT. Got some extra thought about State Monad. Basiclly how to ...

  7. [Functional Programming Monad] Substitute State Using Functions With A State Monad (get, evalWith)

    We take a closer look at the get construction helper and see how we can use it to lift a function th ...

  8. [Functional Programming ADT] Create a Redux Store for Use with a State ADT Based Reducer

    With a well defined demarcation point between Redux and our State ADT based model, hooking up to a R ...

  9. [Functional Programming ADT] Combine Multiple State ADT Based Redux Reducers

    Redux provides a convenient helper for combining many reducers called combineReducer, but it focuses ...

随机推荐

  1. 六十五 async/await

    用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异 ...

  2. LeetCode 344. Reverse String(反转字符串)

    题目描述 LeetCode 344. 反转字符串 请编写一个函数,其功能是将输入的字符串反转过来. 示例 输入: s = "hello" 返回: "olleh" ...

  3. 将excel中的sheet1导入到sqlserver中

    原文地址:C#将Excel数据表导入SQL数据库的两种方法作者:windream 方式一: 实现在c#中可高效的将excel数据导入到sqlserver数据库中,很多人通过循环来拼接sql,这样做不但 ...

  4. Valid Palindrome(LintCode)

    Valid Palindrome Given a string, determine if it is a palindrome, considering only alphanumeric char ...

  5. Linux添加或修改ssh端口

    1)  查看ssh服务是否安装齐全 这里使用”rpm –qa|grep ssh”命令查看. [root@xuexi ~]# rpm -qa | grep ssh libssh2-1.4.3-10.el ...

  6. python 打包详解

    基本步骤: 1. 写setup.py 2. 运行“python setup.py sdist” 3. 在当前目录下会生成文件夹“dist”,打包好的代码就在dist中,以“.tar.gz”的形式被压缩 ...

  7. [BZOJ4822][CQOI2017]老C的任务(扫描线+树状数组)

    4822: [Cqoi2017]老C的任务 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 379  Solved: 203[Submit][Statu ...

  8. 【图论】Popular Cows

    [POJ2186]Popular Cows Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 34752   Accepted: ...

  9. 【分块】计蒜客17120 2017 ACM-ICPC 亚洲区(西安赛区)网络赛 G. Xor

    题意:给一棵树,每个点有权值.q次询问a,b,k,问你从a点到b点,每次跳距离k,权值的异或和? 预处理每个点往其根节点的路径上隔1~sqrt(n)的距离的异或和,然后把询问拆成a->lca(a ...

  10. 【模拟退火】poj2420 A Star not a Tree?

    题意:求平面上一个点,使其到给定的n个点的距离和最小,即费马点. 模拟退火的思想是随机移动,然后100%接受更优解,以一定概率接受更劣解.移动的过程中温度缓慢降低,接受更劣解的概率降低. 在网上看到的 ...