Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7cff5f8

Browse files
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>

‎docs/src/main/tut/examples/example.tsx

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)

AltStyle によって変換されたページ (->オリジナル) /