Skip to content

Commit 7cff5f8

Browse files
authored
Merge pull request #52 from reactive-react/typeclasses
Typeclasses
2 parents fc1fc96 + 6ac7c19 commit 7cff5f8

40 files changed

+1540
-825
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ root = true
55
indent_style = tab
66
indent_size = 2
77

8-
[*.ts, *.tsx]
8+
[*.{ts,tsx}]
99
indent_style = space
1010
indent_size = 2
1111
end_of_line = lf

Makefile

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,49 @@
1-
docs = ./docs/**/*
1+
docsdir = ./docs/**/*
22
browserify = ./node_modules/.bin/browserify
33
watchify = ./node_modules/.bin/watchify
44
uglify = ./node_modules/.bin/uglifyjs
55

6+
test: unit integrate
7+
8+
build: lib/**/*.js
9+
10+
lib/**/*.js: src/**/*.ts
11+
tsc
12+
613
lib/%.js: src/%.ts
714
tsc
815

9-
all: docs/src/main/tut/examples/example.js browser
16+
all: test dist
1017

11-
.PHONY: test
12-
test: lib/**/*.js test/*.js docs/src/main/tut/examples/example.js
18+
.PHONY: test build unit integrate dist docs docs/publish
19+
20+
unit: build
1321
yarn test
1422

23+
integrate: build test/*.js docs/src/main/tut/examples/example.js
24+
mocha test/test.js
25+
1526
docs/src/main/tut/examples/example.js: docs/src/main/tut/examples/example.tsx
1627
$(browserify) -p [tsify -p tsconfig.examples.json] docs/src/main/tut/examples/example.tsx -o docs/src/main/tut/examples/example.js
1728

1829
watch/example: docs/src/main/tut/examples/example.tsx
1930
$(watchify) -p [tsify -p tsconfig.examples.json] -t envify docs/src/main/tut/examples/example.tsx -dv -o docs/src/main/tut/examples/example.js
2031

21-
browser: dist/xreact.min.js dist/xreact-most.min.js dist/xreact-rx.min.js
22-
23-
dist/xreact.js: lib/index.js
24-
env NODE_ENV=production $(browserify) -t browserify-shim -t envify -x ./lib/xs lib/index.js -s xreact -o $@
32+
dist: dist/xreact.min.js dist/xreact-most.min.js dist/xreact-rx.min.js
2533

26-
dist/xreact-most.js: lib/xs/most.js
27-
env NODE_ENV=production $(browserify) -t browserify-shim -t envify -r ./lib/xs lib/xs/most.js -o $@
34+
dist/xreact.js: lib/index.js dist/xreact-most.js dist/xreact-rx.js
35+
env NODE_ENV=production $(browserify) -t browserify-shim -t envify -x ./lib/xs $< -s xreact -o $@
2836

29-
dist/xreact-rx.js: lib/xs/rx.js
30-
env NODE_ENV=production $(browserify) -t browserify-shim -t envify -r ./lib/xs lib/xs/rx.js -o $@
37+
dist/xreact-%.js: lib/xs/%.js
38+
env NODE_ENV=production $(browserify) -t browserify-shim -t envify -r ./lib/xs $< -o $@
3139

3240
dist/%.min.js: dist/%.js
3341
env NODE_ENV=production $(uglify) -c dead_code $(basename $(basename $@)).js -o $@
3442

35-
docs: $(docs)
36-
sbt makeMicrosite
43+
docs: $(docsdir)
44+
sbt "project docs" makeMicrosite
3745

38-
docs/publish: $(docs)
46+
docs/publish: $(docsdir)
3947
sbt "project docs" publishMicrosite
4048

4149
clean:

docs/src/main/tut/Get-Started.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ position: 1
2020
Sorry we don't have a **book** to document how to use xreact, and I don't really need to,
2121
there's only 3 things you should notice when using xreact, I'll explain by a simple counter app.
2222

23-
<iframe src="https://www.webpackbin.com/bins/-KsSVkmGpVrLlwylSrkE" frameborder="0" width="100%" height="500"></iframe>
23+
<p data-height="265" data-theme-id="light" data-slug-hash="gGqvBW" data-default-tab="js,result" data-user="jcouyang" data-embed-version="2" data-pen-title="XREACT" class="codepen">See the Pen <a href="https://codepen.io/jcouyang/pen/gGqvBW/">XREACT</a> by Jichao Ouyang (<a href="https://codepen.io/jcouyang">@jcouyang</a>) on <a href="https://codepen.io">CodePen</a>.</p>
24+
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
2425

2526
### 1. Define a simple stateless View component
2627

@@ -101,4 +102,12 @@ render(
101102

102103
If you are TypeScript user and want to enjoy a type safe counter app, it's simple to do so since xReact is written 100% in TypeScript.
103104

104-
<iframe src="https://www.webpackbin.com/bins/-KsSYQVTkFjd_MQon3b9" frameborder="0" width="100%" height="500"></iframe>
105+
<p data-height="265" data-theme-id="light" data-slug-hash="jGdzYR" data-default-tab="js,result" data-user="jcouyang" data-embed-version="2" data-pen-title="XREACT Counter in TypeScript" class="codepen">See the Pen <a href="https://codepen.io/jcouyang/pen/jGdzYR/">XREACT Counter in TypeScript</a> by Jichao Ouyang (<a href="https://codepen.io/jcouyang">@jcouyang</a>) on <a href="https://codepen.io">CodePen</a>.</p>
106+
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
107+
108+
## Fantasy 🌈 Counter
109+
110+
If you come from 🌈 fantasy land, you cound try fantasy version of xreact in less verbose and more functional scheme. [More about FantasyX](./Fantasy.html)
111+
112+
<p data-height="381" data-theme-id="light" data-slug-hash="wrNjNM" data-default-tab="js,result" data-user="jcouyang" data-embed-version="2" data-pen-title="XREACT FANTASY COUNTER" class="codepen">See the Pen <a href="https://codepen.io/jcouyang/pen/wrNjNM/">XREACT FANTASY COUNTER</a> by Jichao Ouyang (<a href="https://codepen.io/jcouyang">@jcouyang</a>) on <a href="https://codepen.io">CodePen</a>.</p>
113+
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
Lines changed: 94 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,54 @@
11
import * as React from 'react';
22
import { render } from 'react-dom';
33
import '../../../../../src/xs/rx'
4-
import { pure, lift2, X, xinput, fromEvent, traverse, fold } from '../../../../../src'
4+
import { Applicative, lift2,Semigroup, Functor, map, Traversable, FlatMap } from '../../../../../src/fantasy'
5+
import {X} from '../../../../../src'
56
function xmount(component, dom) { render(React.createFactory(X)({}, component), dom) }
67

78
let mult = (x:number,y: number) => x * y
8-
let Xeg1 = lift2(mult)(pure(6), pure(5))
9+
let Xeg1 = lift2<"FantasyX",number, number, number>(mult)(Applicative.FantasyX.pure(6), Applicative.FantasyX.pure(5))
910

1011
let ViewEg1 = props => <p className="result">{props.product}</p>
1112

12-
let Eg1 = Xeg1.map(a=>({product: a})).apply(ViewEg1)
13+
let Eg1 = Functor.FantasyX.map(a=>({product: a}), Xeg1).apply(ViewEg1)
1314

1415
xmount(<Eg1/>, document.getElementById('eg1') )
1516

17+
import {Xstream} from '../../../../../src/fantasy/xstream';
18+
1619
function strToInt(x) {return ~~x}
20+
type Intent = {type:string, value:number}
21+
let XSinput1 = Xstream.fromEvent('change', 'n1', '5')
22+
let XSinput2 = Xstream.fromEvent('change', 'n2', '6')
1723

18-
let Xeg2 = lift2(mult)(
19-
fromEvent('change', 'n1', '5').map(strToInt),
20-
fromEvent('change', 'n2', '6').map(strToInt)
21-
)
24+
let Xeg2 = lift2<"Xstream", number, number, number>(mult)(
25+
Functor.Xstream.map(strToInt, XSinput1),
26+
Functor.Xstream.map(strToInt, XSinput2)
27+
).toFantasyX()
28+
.map(x=>({product: x}))
2229

2330
let ViewEg2 = props => <section>
24-
<p><input type="number" name="n1" onChange={props.actions.fromEvent} defaultValue="5"/></p>
25-
<p><input type="number" name="n2" onChange={props.actions.fromEvent} defaultValue="6"/></p>
26-
<p><span className="result">{props.product}</span></p>
27-
</section>
31+
<p><input type="number" name="n1" onChange={props.actions.fromEvent} defaultValue="5"/></p>
32+
<p><input type="number" name="n2" onChange={props.actions.fromEvent} defaultValue="6"/></p>
33+
<p><span className="result">{props.product}</span></p>
34+
</section>
2835

29-
let Eg2 = Xeg2.map(a=>({product: a})).apply(ViewEg2)
36+
let Eg2 = Xeg2.apply(ViewEg2)
3037

3138
xmount(<Eg2/>, document.getElementById('eg2') )
3239

33-
let Xeg3 = fromEvent('change', 'firstName', 'Jichao')
34-
.concat(pure(' '))
35-
.concat(fromEvent('change', 'lastName', 'Ouyang'))
36-
40+
let Xeg3 = Semigroup.Xstream.concat(
41+
Semigroup.Xstream.concat(
42+
Xstream.fromEvent('change', 'firstName', 'Jichao'),
43+
Applicative.Xstream.pure(' ')
44+
),
45+
Xstream.fromEvent('change', 'lastName', 'Ouyang')
46+
).toFantasyX()
3747
let ViewEg3 = props => <section>
38-
<p><input type="text" name="firstName" onChange={props.actions.fromEvent} defaultValue="Jichao" /></p>
39-
<p><input type="text" name="lastName" onChange={props.actions.fromEvent} defaultValue="Ouyang"/></p>
40-
<p><span className="result">{props.semigroup}</span></p>
41-
</section>
48+
<p><input type="text" name="firstName" onChange={props.actions.fromEvent} defaultValue="Jichao" /></p>
49+
<p><input type="text" name="lastName" onChange={props.actions.fromEvent} defaultValue="Ouyang"/></p>
50+
<p><span className="result">{props.semigroup}</span></p>
51+
</section>
4252

4353
let Eg3 = Xeg3.map(a=>({semigroup: a})).apply(ViewEg3)
4454

@@ -48,10 +58,11 @@ function sum(list){
4858
return list.reduce((acc,x)=> acc+x, 0)
4959
}
5060
let list = ['1', '2', '3', '4', '5', '6', '7']
51-
let Xeg4 = traverse(
52-
(defaultVal, index)=>(fromEvent('change', 'traverse'+index, defaultVal)),
53-
list
54-
).map(xs=>xs.map(strToInt))
61+
let Xeg4 = Traversable.Array.traverse<'Xstream', string, string>('Xstream')(
62+
(defaultVal, index) => (Xstream.fromEvent('change', 'traverse' + index, defaultVal)),
63+
list
64+
).toFantasyX()
65+
.map(xs => xs.map(strToInt))
5566
.map(sum)
5667

5768
let ViewEg4 = props => <section>
@@ -67,91 +78,91 @@ let Eg4 = Xeg4.map(a=>({sum: a})).apply(ViewEg4)
6778
xmount(<Eg4/>, document.getElementById('eg4') )
6879

6980
function bmiCalc(weight, height) {
70-
return {
71-
weight: weight,
72-
height: height,
73-
result:fetch(`https://gist.github.com.ru/jcouyang/edc3d175769e893b39e6c5be12a8526f?height=${height}&weight=${weight}`)
74-
.then(resp => resp.json())
75-
.then(json => json.result)
76-
}
81+
return fetch(`https://gist.github.com.ru/jcouyang/edc3d175769e893b39e6c5be12a8526f?height=${height}&weight=${weight}`)
82+
.then(resp => resp.json())
83+
.then(json => json.result)
7784
}
7885

79-
let Xeg5 = lift2(bmiCalc)(
80-
fromEvent('change', 'weight', '70'),
81-
fromEvent('change', 'height', '175')
86+
let xweigth = Xstream.fromEvent('change', 'weight', '70')
87+
let xheight = Xstream.fromEvent('change', 'height', '175')
88+
89+
let promiseXstream = lift2<"Xstream", string, string, Promise<any>>(bmiCalc)(
90+
xweigth,
91+
xheight
92+
)
93+
94+
let Xeg5 = FlatMap.Xstream.flatMap(Xstream.fromPromise, promiseXstream)
95+
.toFantasyX()
96+
97+
let ViewEg5 = props => (
98+
<div>
99+
<label>Height: {props.height} cm
100+
<input type="range" name="height" onChange={props.actions.fromEvent} min="150" max="200" defaultValue={props.height} />
101+
</label>
102+
<label>Weight: {props.weight} kg
103+
<input type="range" name="weight" onChange={props.actions.fromEvent} min="40" max="100" defaultValue={props.weight} />
104+
</label>
105+
<p>HEALTH: <span>{props.health}</span></p>
106+
<p>BMI: <span className="result">{props.bmi}</span></p>
107+
</div>
82108
)
83109

84-
let ViewEg5 = props => (
85-
<div>
86-
<label>Height: {props.height} cm
87-
<input type="range" name="height" onChange={props.actions.fromEvent} min="150" max="200" defaultValue={props.height} />
88-
</label>
89-
<label>Weight: {props.weight} kg
90-
<input type="range" name="weight" onChange={props.actions.fromEvent} min="40" max="100" defaultValue={props.weight} />
91-
</label>
92-
<p>HEALTH: <span>{props.health}</span></p>
93-
<p>BMI: <span className="result">{props.bmi}</span></p>
94-
</div>
95-
)
96-
97-
let Eg5 = Xeg5.apply(ViewEg5)
110+
let Eg5 = Xeg5.apply(ViewEg5)
98111

99112
xmount(<Eg5/>, document.getElementById('eg5') )
100113

101-
let Xeg6 = fold(
102-
(acc:number,i: number) => acc+i,
103-
0,
104-
fromEvent('click', 'increment').map(x=>1)
105-
)
114+
let Xeg6 = Xstream.fromEvent('click', 'increment')
115+
.toFantasyX<{count:number}>()
116+
.map(x => 1)
117+
.foldS((acc, a) => {
118+
return { count: (acc.count||0) + a }})
106119

107120
let ViewEg6 = props => <p>
108-
<span className="result">{props.count}</span>
109-
<input type="button" name="increment" value="+1" onClick={e=>props.actions.fromEvent(e)} />
110-
</p>
121+
<span className="result">{props.count || 0}</span>
122+
<input type="button" name="increment" value="+1" onClick={e=>props.actions.fromEvent(e)} />
123+
</p>
111124

112-
let Eg6 = Xeg6.map(a=>({count: a})).apply(ViewEg6)
125+
let Eg6 = Xeg6.apply(ViewEg6)
113126

114127
xmount(<Eg6/>, document.getElementById('eg6') )
115128

116-
let Xeg7 = fold(
117-
(acc:number,i: number) => acc+i,
118-
0,
119-
fromEvent('click', 'increment').map(x=>1)
120-
.merge(
121-
fromEvent('click', 'decrement').map(x=>-1)
122-
)
123-
)
129+
let Xeg7 = Xstream.fromEvent('click', 'decrement')
130+
.toFantasyX<{count:number}>()
131+
.map(x => -1)
132+
.foldS((acc, a) => {
133+
return { count: (acc.count||0) + a }})
124134

125-
let ViewEg7 = props => <p>
126-
<input type="button" name="decrement" value="-" onClick={e=>props.actions.fromEvent(e)} />
127-
<span className="result">{props.count}</span>
128-
<input type="button" name="increment" value="+" onClick={e=>props.actions.fromEvent(e)} />
129-
</p>
135+
let ViewEg7 = props => <p>
136+
<input type="button" name="decrement" value="-" onClick={e=>props.actions.fromEvent(e)} />
137+
<span className="result">{props.count || 0}</span>
138+
<input type="button" name="increment" value="+" onClick={e=>props.actions.fromEvent(e)} />
139+
</p>
130140

131-
let Eg7 = Xeg7.map(a=>({count: a})).apply(ViewEg7)
141+
let Eg7 = Xeg7.merge(Xeg6).apply(ViewEg7)
132142

133143
xmount(<Eg7/>, document.getElementById('eg7') )
134144

135145
const actions = ['-1', '+1', 'reset']
136-
let Xeg8 = fold(
137-
(acc, i) => {
138-
switch(i) {
139-
case '-1': return acc-1
140-
case '+1': return acc+1
141-
case 'reset': return 0
142-
default: acc
146+
let Xeg8 =
147+
actions.map((action)=>Xstream.fromEvent('click', action).toFantasyX<{count:number}>())
148+
.reduce((acc,a)=>acc.merge(a))
149+
.foldS((acc, i) => {
150+
acc.count = acc.count || 0
151+
switch(i) {
152+
case '-1': return {count: acc.count -1}
153+
case '+1': return {count: acc.count +1}
154+
case 'reset': return {count: 0}
155+
default: acc
156+
}
143157
}
144-
},
145-
0,
146-
actions.map((action)=>fromEvent('click', action))
147-
.reduce((acc,a)=>acc.merge(a)))
158+
)
148159

149160
let ViewEg8 = props => <p>
150161
<span className="result">{props.count}</span>
151162
{actions.map(action=>
152163
<input type="button" name={action} value={action} onClick={e=>props.actions.fromEvent(e)} />)}
153164
</p>
154165

155-
let Eg8 = Xeg8.map(a=>({count: a})).apply(ViewEg8)
166+
let Eg8 = Xeg8.apply(ViewEg8)
156167

157168
xmount(<Eg8/>, document.getElementById('eg8') )

0 commit comments

Comments
 (0)