Skip to content

Instantly share code, notes, and snippets.

@CreaturePhil
Created February 3, 2017 05:34
Show Gist options
  • Save CreaturePhil/41ff7a15cda2533a2d42f1e6344cbe7c to your computer and use it in GitHub Desktop.
Save CreaturePhil/41ff7a15cda2533a2d42f1e6344cbe7c to your computer and use it in GitHub Desktop.
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import './index.css';
const Comp = g =>
({
fold: g,
contramap: f =>
Comp(x => g(f(x))),
concat: other =>
Comp(x => <div>{g(x)} {other.fold(x)}</div>)
})
// Object -> JSX
const ProfileLink = user =>
<a href={`/users/${user.id}`}>{user.name}</a>
// String -> JSX
const Heading = str =>
<h1>Now Viewing {str}</h1>
// Comp
const Title = Comp(Heading).contramap(s => s.pageName)
// f = s => s.pageName :: Object -> String
// g = Heading :: String -> JSX
// g' = x => g(f(x)) :: Object -> Comp
// intermediate process: g . f :: Object -> String -> JSX
// g . f :: Object -> JSX
const Link = Comp(ProfileLink).contramap(s => s.currentUser)
// const App = Title
// .concat(Link)
// .concat(Link)
// .concat(Title)
// referential transparency
// Title.fold({ pageName: 'Home '})
// g({pageName: 'Home'})
// ({pageName: 'Home'}) => g(f(x))
// Heading((s => s.pageName)({pageName: 'Home'}))
// Heading('Home')
// <h1> Now Viewing {str}</h1>
// So basically, it calls Comp(g).contramap(f)
// f is called first then and it passes it into g
// compose(g, f) goes from f to g
// Comp(compose(g, f))
// concat adds another element as a non parent-child
// not parent/cild of each other. just next to each other
// can think of concat as a list
// Reducer :: (a, b) -> a
// contramap runs on each element beforehand
// map runs on the accumulator afterwards
// concat is basically combine reducers
const Reducer = g =>
({
fold: g,
contramap: f =>
Reducer((acc, x) => g(acc, f(x))),
map: f =>
Reducer((acc, x) => f(g(acc, x))),
concat: o =>
Reducer((acc, x) => o.fold(g(acc, x), x))
})
const r = Reducer((acc, x) => acc.concat(x))
.contramap(x => `The number is ${x}`)
.map(x => x + '! ')
// Reducer((acc, x) => acc.concat(x))
// g = (acc, x) => acc.concat(x)
// contramap calls f(x) and puts it pack as the 2nd argument to g
// contramap:
// f = x => `The number is ${x}`
// return: Reducer((acc, x) => g(acc, f(x)))
// g(acc, f(x)) = ((acc, x) => acc.concat(x))(acc, (x => `The number is ${x}`)(x))
// new g = (acc, x) => ((acc, x) => acc.concat(x))(acc, (x => `The number is ${x}`)(x))
// map calls g and pass it to f
// map:
// f = x => x + '!'
// return: Reducer((acc, x) => f(g(acc, x)))
// g(acc, x) = ((acc, x) => ((acc, x) => acc.concat(x))(acc, (x => `The number is ${x}`)(x)))(acc, x)
// f = (x => x + '!')((acc, x) => ((acc, x) => acc.concat(x))(acc, (x => `The number is ${x}`)(x)))(acc, x)
// new g is (acc, x) => f(g(acc, x)) or
// (acc, x) => (x => x + '!')((acc, x) => ((acc, x) => acc.concat(x))(acc, (x => `The number is ${x}`)(x)))(acc, x)
console.log([1,2,3].reduce(r.fold, ''))
// calling fold
// g = (acc, x) => (x => x + '!')((acc, x) => ((acc, x) => acc.concat(x))(acc, (x => `The number is ${x}`)(x)))(acc, x)
// g('', 1) where acc = '' and x = 1
// contramap: The number is 1
// pass it back to the (acc, x) => acc.concat
// The number is 1
// map: The number is 1!
// The number is 1!
// The number is 1! The number is 2! The number is 3!
const r1 = Reducer((acc, x) => acc.concat(x))
.map(x => `The number is ${x}`)
.map(x => x + '! ')
console.log('r1', [1,2,3].reduce(r1.fold, ''))
// show diff
// for each input it is mapped to
const r2 = Reducer((acc, x) => acc.concat(x))
.map(x => x + '! ')
.contramap(x => `The number is ${x}`)
console.log('r2', [1,2,3].reduce(r2.fold, ''))
const appReducer = Reducer((state, action) => {
switch (action.type) {
case 'set_visibility_filter':
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
})
const todoReducer = Reducer((state, action) => {
switch (action.type) {
case 'new_todo':
const t = { id: 0, title: action.payload.title }
return Object.assign({}, state, {
todos: state.todos.concat(t)
})
default:
return state
}
})
// Hoc :: Component -> Component
const Hoc = g =>
({
fold: g,
concat: other =>
Hoc(x => g(other.fold(x)))
})
// isomorphism
const classToFn = C =>
(props) => <C {...props} />
const withReducer = (stateName, dispatchName, reducer, initialState) =>
BaseComponent => {
return class extends Component {
constructor(props) {
super(props);
this.state = {stateValue: initialState};
this.dispatch = this.dispatch.bind(this);
}
dispatch(action) {
const state = ({ stateValue }) => ({stateValue: reducer(stateValue, action)});
this.setState(state);
}
render() {
const props = Object.assign({}, this.props, {
[stateName]: this.state.stateValue,
[dispatchName]: this.dispatch
});
return (
<div>
<BaseComponent {...props} />
</div>
);
}
}
}
const todoApp = appReducer.concat(todoReducer)
.contramap(action => Object.assign({filter: 'all'}, action))
.map(s => Object.assign({}, s, {lastUpdated: Date()}))
const aState = {visibilityFilter: 'complete'}
const aAction = {type: 'set_visibility_filter'}
const res = todoApp.fold(aState, aAction)
console.log(res)
const hoc = Hoc(withReducer('state', 'dispatch', todoApp.fold, {todos: []}))
const Todos = hoc.fold(({ state, dispatch }) =>
<div>
<span>Filter: {state.visibilityFilter}</span>
<ul>
{ state.todos.map((t, i) => <li key={i}>{t.title}</li>) }
</ul>
<button onClick={() =>
dispatch({ type: 'new_todo', payload: {title: 'New todo'}})}>
Add Todo
</button>
<button onClick={() =>
dispatch({ type: 'set_visibility_filter' })}>
Set Visibility
</button>
<div>
<pre>
<code>{JSON.stringify(state, null, 2)}</code>
</pre>
</div>
</div>
)
// classToFn is not really use, Comp here not really use either xaa in todo app
const TodoComp = Comp(classToFn(Todos))
const App = Title.contramap(s => s.pageName)
.concat(TodoComp)
.concat(Link)
.fold({ pageName: 'Home', currentUser: {id: 2, name: 'Phil La'} })
ReactDOM.render(
App,
document.getElementById('root')
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment