[Functional Programming ADT] Create State ADT Based Reducers (applyTo, Maybe)
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)的更多相关文章
- [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 ...
- [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 ...
- [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 ...
- [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 ...
- [Functional Programming] Reader with Async ADT
ReaderT is a Monad Transformer that wraps a given Monad with a Reader. This allows the interface of ...
- [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 ...
- [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 ...
- [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 ...
- [Functional Programming ADT] Combine Multiple State ADT Based Redux Reducers
Redux provides a convenient helper for combining many reducers called combineReducer, but it focuses ...
随机推荐
- 动态创建timer
Private timer:Ttimer;procedure MyTimerDo(Sender:Tobject);procedure create ; timer:=TtIMER.Create; ...
- JS中事件绑定问题
今天编写代码时遇到一个问题,我的判断语句(IFLESE)老是顺序执行结束后又跳到中间的语句里去执行了,找了半天没发现问题,最后才发现是事件绑定闹得鬼,不多说,先上代码为敬. JSP里 <butt ...
- [hdu-4946] Area of Mushroom 计算几何 凸包
大致题意: 平面上有n个人,给你每个人的坐标和一个速度v,如果某个人比其他所有人都先到达某点,则该点就被这个人掌控,求谁掌控者无限大的面积. 首先 速度最大的人,抛弃其他人,速度小的人必定无法得到无限 ...
- jq函数绑定与解绑
最近学到几个新的jq函数 1.bind()绑定函数 2.unbind()解绑函数 3.add() .给元素追加字符串 4.addClass() 给某元素增加class属性值
- Kali Linux WPScan更新到2.9.3
Kali Linux WPScan更新到2.9.3 WPScan是Kali Linux内置的一款Web漏洞扫描工具,专门扫描WordPress模版构建的网站.该工具最近更新到2.9.3.在新版本中 ...
- SQLSEVER 中的那些键和约束
SQL Server中有五种约束类型,分别是 PRIMARY KEY约束.FOREIGN KEY约束.UNIQUE约束.DEFAULT约束.和CHECK约束.查看或者创建约束都要使用到 Microso ...
- [BZOJ4025]二分图(线段树分治,并查集)
4025: 二分图 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 2191 Solved: 800[Submit][Status][Discuss] ...
- [BZOJ3576]江南乐
挺好的题 我们算出每个数的sg值后异或起来即可 对于$n$,我们要求$sg_n$ 朴素的想法是枚举把$n$个石子分成$m$堆,有$m-n\%m$堆大小为$\left\lfloor\frac nm\ri ...
- JVM 参数配置及详解 -Xms -Xmx -Xmn -Xss 调优总结
堆大小设置 JVM 中最大堆大小有三方面限制: ①.相关操作系统的数据模型(32-bt还是64-bit)限制; ②.系统的可用虚拟内存限制; ③.系统的可用物理内存限制. 32位系统 下,一般限制在1 ...
- Generator函数(二)
for...of循环 1.for...of循环可以自动遍历Generator函数,不需要再调用next方法 function* helloWorldGenerator(){ yield 'hello' ...