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. nodejs调试基础【转载】

    nodejs调试基础[转载] 看到了一片不错的文章: 作者:前端求生之路 链接:nodejs调试基础[转载]

  2. 深刻剖析VuGen脚本录制原理

    扩展:深刻剖析VuGen脚本录制原理 LR中的VuGen组件,主要扮演一个proxy server的角色,在录制脚本时,记录下用户和服务器交互,然后自动生成脚本语言.在接下来的重放,或者大批量地加压时 ...

  3. str 编码

    你需要的是让编码用实际编码而不是 ascii    1 对需要 str->unicode 的代码,可以在前边写上  import sys  reload(sys)  sys.setdefault ...

  4. 字符串Hash相关

    其实也并不是什么特别难的算法,但是我个人实在是不太喜欢字符串之类的东西(字符串神马的真的是麻烦),于是一直拖着不想看,然后模板题之类的也懒得做. Hash的思想其实也没什么复杂的,就是给定一系列字符串 ...

  5. Flask实战第55天:cms轮播图上传到七牛功能完成

    登录七牛云,进入“对象存储”, 新建存储空间(Bucket), 我创建的空间命名为flask-bbs 创建完Bucket,七牛会给我们提供一个测试域名,生产环境中,我们需要绑定自己的域名 在个人面板中 ...

  6. Mybatis 使用Mybatis时实体类属性名和表中的字段名不一致

    开发中,实体类中的属性名和对应的表中的字段名不一定都是完全相同的,这样可能会导致用实体类接收返回的结果时导致查询到的结果无法映射到实体类的属性中,那么该如何解决这种字段名和实体类属性名不相同的冲突呢? ...

  7. 【POJ 2186】Popular Cows

    http://poj.org/problem?id=2186 tarjan求强连通分量. 因为SD省选用WinXP+Cena评测而且不开栈,所以dfs只好写手动栈了. 写手动栈时思路清晰一点应该是不会 ...

  8. 【ZJOI2017】线段树

    题目描述 线段树是九条可怜很喜欢的一个数据结构,它拥有着简单的结构.优秀的复杂度与强大的 功能,因此可怜曾经花了很长时间研究线段树的一些性质. 最近可怜又开始研究起线段树来了,有所不同的是,她把目光放 ...

  9. 2017 Multi-University Training 2 解题报告

    Is Derek lying? Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)T ...

  10. python基础之数据类型之元组和字典

    四.元组 1.用途:元组是不可变的列表,能存多个值,但只能取,不能改 2.定义:name = (‘alex’, ’egon’, ‘wxx’) 在()内用,分割开,可存放任意类型的值 强调:x = (‘ ...