Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Last active April 18, 2017 19:14
Show Gist options
  • Select an option

  • Save Heimdell/ea6146afbb94ed83877233041177c8a3 to your computer and use it in GitHub Desktop.

Select an option

Save Heimdell/ea6146afbb94ed83877233041177c8a3 to your computer and use it in GitHub Desktop.
<!DOCTYPE HTML>
<html>
<head>
<script src="./classy-frp.js">
</script>
<script>
var digits = Event.new();
var dots = Event.new();
var ops = Event.new();
var enters = Event.new();
var cleans = Event.new();
var bsps = Event.new();
var digit = (n) => digits.feed(n)
var dot = () => dots.feed(1)
var op = (o) => ops.feed(o)
var enter = () => enters.feed(1)
var clean = () => cleans.feed(1)
var bsp = () => bsps.feed(1)
var input = Event.group({
digit: digits,
dot: dots,
op: ops,
enter: enters,
clean: cleans,
bsp: bsps
})
var initial = {up: "", bottom: "", op: undefined, status: "fill_up"}
function handleForInput(event) {
return (input) => {
console.log({event, input});
if (event.dot) {
if (!input.includes('.')) {
return input === ""? "0." : input + "."
}
}
if (event.digit) {
console.log("digit!");
return input + event.digit
}
if (event.bsp) {
return input.slice(0, -1)
}
return input
}
}
var stage = input.reduce(initial, (state, event) => {
if (event.clean) {
return initial
}
switch (state.status) {
case "fill_up":
var handled = Utils.modify(state, "up", handleForInput(event))
if (event.op) {
var status = Utils.set(handled, "status", "fill_down")
var opSet = Utils.set(status, "op", event.op)
return opSet
}
return handled;
case "fill_down":
var handled = Utils.modify(state, "bottom", handleForInput(event))
if (event.op || event.enter && handled.op) {
return {
up: eval(handled.up + (handled.op || event.op) + handled.bottom),
bottom: "",
op: event.op,
status: "fill_down"
}
}
return handled;
}
return state
}).log("stage")
stage.subscribe(state => {
buffer.value = state.up
buffer2.value = state.bottom
})
</script>
</head>
<body>
<table>
<tr>
<td colspan="4">
<input id="buffer"/>
</td>
</tr>
<tr>
<td colspan="4">
<input id="buffer2"/>
</td>
</tr>
<tr>
<td>
<button onclick="digit('1')">1</button>
</td>
<td>
<button onclick="digit('2')">2</button>
</td>
<td>
<button onclick="digit('3')">3</button>
</td>
<td>
<button onclick="op('%')">%</button>
</td>
</tr>
<tr>
<td>
<button onclick="digit('4')">4</button>
</td>
<td>
<button onclick="digit('5')">5</button>
</td>
<td>
<button onclick="digit('6')">6</button>
</td>
<td>
<button onclick="op('*')">x</button>
</td>
</tr>
<tr>
<td>
<button onclick="digit('7')">7</button>
</td>
<td>
<button onclick="digit('8')">8</button>
</td>
<td>
<button onclick="digit('9')">9</button>
</td>
<td>
<button onclick="op('+')">+</button>
</td>
</tr>
<tr>
<td>
<button onclick="digit('0')">0</button>
</td>
<td>
<button onclick="dot()">.</button>
</td>
<td>
<button onclick="enter()">=</button>
</td>
<td>
<button onclick="op('-')">-</button>
</td>
</tr>
<tr>
<td colspan="2">
<button onclick="clean()">Clean</button>
</td>
<td colspan="2">
<button onclick="bsp()">&lt;-</button>
</td>
</tr>
</table>
</body>
</html>
var setImmediate = setImmediate || ((f) => setTimeout(f, 0))
class Utils {
static unsnoc(array) {
return {
init: array.slice(0, -1),
last: array[array.length - 1],
}
}
static set(o, path, x) {
return Utils.modify(o, path, _ => x)
}
static modify(o, path, f) {
var crumbs = path.split()
function aux(o, piece, ...path) {
return piece?
Object.assign({}, o, {[piece]: aux(o[piece], ...path)}):
f(o)
}
return aux(o, ...crumbs)
}
}
class Event {
static new() {
return new Event()
}
constructor() {
this.subscribers = []
}
subscribe(callback) {
this.subscribers.push(callback)
}
feed(event) {
this.subscribers.forEach(sink => {
sink(event)
})
}
transform(commutator) {
var result = Event.new()
this.subscribe(event => {
setImmediate(() => {
commutator(result.feed.bind(result), event)
})
})
return result
}
map(mapper) {
return this.transform((feed, event) => {
feed(mapper(event))
})
}
filter(predicate) {
return this.transform((feed, event) => {
if (predicate(event)) {
feed(event)
}
})
}
reduce(acc, add) {
return this.transform((feed, event) => {
feed(acc = add(acc, event))
})
}
delay(ms) {
return this.transform((feed, event) => {
setTimeout(() => feed(event), ms)
})
}
throttle(ms) {
var comesThrough = true;
return this.transform((feed, event) => {
if (comesThrough) {
comesThrough = false
feed(event)
setTimeout(() => {
comesThrough = true
}, ms)
}
})
}
mergeWith(event) {
var result = Event.new()
this .subscribe(event => result.feed(event))
event.subscribe(event => result.feed(event))
return result
}
static merge([event, ...events]) {
return events.reduce((a, b) => a.mergeWith(b), event)
}
static group(group) {
var keyed =
Object.keys(group).map(key =>
group[key].map(x =>
({[key]: x})
)
)
return Event.merge(keyed)
}
behavior() {
return Behavior.of(this)
}
sample(...args) {
const {init: behaviors, last: f} = Utils.unsnoc(args)
return this.map(x => f(x, ...behaviours.map(it => it.now())))
}
compact() {
return this.filter(x => undefined !== x)
}
split(tags) {
var tagged =
tags.map(tag => ({
tag,
event: this.map(event => event[tag]).compact()
}))
return tagged.reduce(
(acc, {tag, event}) =>
Object.assign(acc, {[tag]: event}),
{}
)
}
log(mark) {
this.subscribe(e => console.log({[mark]: e}))
return this
}
}
class Behavior {
static of(event) {
return new Behavior(event)
}
constructor(event) {
event.subscribe(value => {
this.value = value
})
}
now() {
return this.value
}
sampleOn(event) {
return event.map(_ => this.now())
}
applyTo(event, f) {
return event.map(x => f(this.now(), x))
}
}
// var input = Event.new()
// var input2 = Event.new()
//
// var grouped = Event.group({a: input, b: input2.delay(50)})
//
// console.log(grouped)
//
// grouped.subscribe(x => console.log(x));
//
// const {a: as, b: bs} = grouped.split(['a', 'b']);
//
// as.subscribe(x => console.log("A:", x));
// bs.subscribe(x => console.log("B:", x));
//
// [1,2,3,4,5,6,7,8,9,10].forEach(i => setTimeout(() => {
// input.feed(i * 2)
// input2.feed(i * 2 + 1)
// }, i * 100))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment