[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 ...
随机推荐
- 洛谷——P4018 Roy&October之取石子
P4018 Roy&October之取石子 题目背景 Roy和October两人在玩一个取石子的游戏. 题目描述 游戏规则是这样的:共有n个石子,两人每次都只能取p^kpk个(p为质数,k为自 ...
- Codeforces Round #476 (Div. 2) [Thanks, Telegram!] ABCDE
修仙场,没脑子,C边界判错一直在写mdzz..D根本没怎么想. 第二天起来想了想D这不是水题吗立马A了.看了看E一开始想分配问题后来发觉想岔了,只需要每次都把树最后分配的点移到最前面就好了. A. P ...
- 【模拟】 Codeforces Round #434 (Div. 1, based on Technocup 2018 Elimination Round 1) C. Tests Renumeration
题意:有一堆数据,某些是样例数据(假设X个),某些是大数据(假设Y个),但这些数据文件的命名非常混乱.要你给它们一个一个地重命名,保证任意时刻没有重名文件的前提之下,使得样例数据命名为1~X,大数据命 ...
- 【找规律】【DFS】Gym - 101174H - Pascal's Hyper-Pyramids
二维下,如果把杨辉三角按照题目里要求的那样摆放,容易发现,第i行第j列的数(从0开始标号)是C(i+j,i)*C(j,j). 高维下也有类似规律,比如三维下,最后一层的数其实是C(i+j+k,i)*C ...
- 20162303实验四 Android程序设计
北京电子科技学院(BESTI) 实 验 报 告 课程:程序设计与数据结构 班级: 1623 姓名: 石亚鑫 学号:20162303 成绩: 2分 指导教师:娄嘉鹏 王志强 实验日期:5月26日 实验密 ...
- lightoj 1229 - Treblecross 博弈论
思路:SG函数 枚举先手的每一个位置是否有必胜. 1)如果出现了XXX则必胜: 2)如果出现了XX或X.X则必败: 3)否则计算后手的sg值和. 代码如下: #include<iostream& ...
- 测试 markdown
PHP 标量类型与返回值类型声明 标量类型声明 默认情况下,所有的PHP文件都处于弱类型校验模式. PHP 7 增加了标量类型声明的特性,标量类型声明有两种模式: 强制模式 (默认) 严格模式 标量类 ...
- Java高级架构师(一)第38节:Nginx的负载均衡模块
负载均衡: 1.热备:如果你有2台服务器,当一台服务器发生事故时,才启用第二台服务器给提供服务.服务器处理请求的顺序:AAAAAA突然A挂啦,BBBBBBBBBBBBBB..... upstream ...
- OpenVPN官方资源下载和官方教程入口
官方资源: https://openvpn.net/index.php/open-source/downloads.html 2.4.4版本下载:(链接: https://pan.baidu.com/ ...
- pcap报文格式
pcap报文格式 pcap报文整体格式 pcap 报文头格式 pcap报文格式,黄色部分为报文头 pcapng报文格式 PCAPNG: PCAP Next Generation Dump File F ...