Skip to content

Instantly share code, notes, and snippets.

@Raynos
Last active December 15, 2015 10:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Raynos/5243195 to your computer and use it in GitHub Desktop.
Save Raynos/5243195 to your computer and use it in GitHub Desktop.

FRP

A one of the features of FRP is the ability to take foreign events (inputs!) and write a function that takes application state & the event and returns new application state.

This is cool because you can it forces you to group and explicitely describe how your inputs effect your state. When combined with only having static inputs it makes your application code flow from input description to application state in a nice linear, readable fashion.

You can also group all these transforms near each and other and have a good oversight of what ACTUALLY changes your application state.

Using JavaScript Objects & Arrays

One example here uses plain Objects & Arrays as your data structure. It uses a utility extend as a bit of sugar to non-destructively shallow extend objects.

This is cool because it's simple. It's just objects and arrays and its immutable which fits nicely with the functional style.

Using an Immutable abstraction

The other example uses a thin Immutable data structure example. The benefits of this are that we can use a patch function that's a bit shorter. Especially for the modifyTodo function.

The other benefit is that the implementation detail level we can use structural-sharing (red black trees) or mutable data structures for performance benefits. Another benefit is that you can efficiently track deltas because they are the arguments to patch functions which aids greatly in delta based rendering.

Which is better?

  • Are Objects / Arrays nicer because it has less abstraction overhead
  • Are Immutable structures nicer because the API is simpler & it's more performant?
var transform = require("signal/transform")
var merge = require("signal/merge")
var foldp = require("signal/foldp")
var EventPool = require("signal/input/event-pool")
var Router = require("signal/input/router")
var Immutable = require("immutable")
// Application Model
var TodoStructure = { id: "", title: "", completed: false, editing: false }
var TodosStructure = { todos: Immutable.hash, route: "all" }
// Inputs
var addTodoPool = EventPool("add-todo")
var toggleAllPool = EventPool("toggle-all")
var modifyTodoPool = EventPool("modify-todo")
var clearCompletedPool = EventPool("clear-completed")
var routes = Router()
function transformState(input, listener) {
return transform(input, function (event) {
return function (state) {
listener(event, state)
}
})
}
// State operations
var addTodo = transformState(addTodoPool.signal, function (todo, state) {
return todo.title === "" ? state : state.patch("todos." + todo.id, todo)
})
var modifyTodo = transformState(modifyTodoPool.signal, function (change, state) {
var path = "todos." + change.id
return change.title === "" ? state.patch(path, null) : state.patch(path, change)
})
var toggleAll = transformState(toggleAllPool.signal, function (toggled, state) {
return state.map("todos", function (todo) {
return todo.patch("completed", toggled)
})
})
var clearCompleted = transformState(clearCompletedPool.signal, function (_, state) {
return state.filter("todos", function (todo) {
return !todo.get("completed")
})
})
var handleRoutes = transformState(routes, function (routeEvent, state) {
return state.patch("route", routeEvent.hash.slice(2) || "all")
})
var input = merge([
addTodo,
modifyTodo,
toggleAll,
clearCompleted,
handleRoutes
])
// Updating the current state
var applicationState = foldp(input, function update(state, modification) {
return modification(state)
}, Immutable(TodosStructure))
// applicationState contains Immutable data structures. Use toJSON() to get
// JS objects out
var appStateObjects = transform(input, function (state) {
return state.toJSON()
})
var extend = require("xtend")
var transform = require("signal/transform")
var merge = require("signal/merge")
var foldp = require("signal/foldp")
var EventPool = require("signal/input/event-pool")
var Router = require("signal/input/router")
// Application Model
var TodoStructure = { id: "", title: "", completed: false, editing: false }
var TodosStructure = { todos: [], route: "all" }
// Inputs
var addTodoPool = EventPool("add-todo")
var toggleAllPool = EventPool("toggle-all")
var modifyTodoPool = EventPool("modify-todo")
var clearCompletedPool = EventPool("clear-completed")
var routes = Router()
// State operations
var addTodo = transform(addTodoPool.signal, function (todo) {
return function (state) {
return todo.title === "" ? state :
extend(state, { todos: state.todos.concat(todo) })
}
})
var modifyTodo = transform(modifyTodoPool.signal, function (change) {
return function (state) {
return extend(state, {
todos: state.todos.reduce(function (todos, todo) {
return change.id !== todo.id ? todos.concat(todo) :
change.title === "" ? todos :
todos.concat(extend(todo, change))
}, [])
})
}
})
var toggleAll = transform(toggleAllPool.signal, function (toggled) {
return function (state) {
return extend(state, { todos: state.todos.map(function (todo) {
return extend(todo, { completed: toggled })
}) })
}
})
var clearCompleted = transform(clearCompletedPool.signal, function () {
return function (state) {
return extend(state, { todos: state.todos.filter(function (todo) {
return !todo.completed
}) })
}
})
var handleRoutes = transform(routes, function (routeEvent) {
return function (state) {
return extend(state, { route: routeEvent.hash.slice(2) || "all" })
}
})
var input = merge([
addTodo,
modifyTodo,
toggleAll,
clearCompleted,
handleRoutes
])
// Updating the current state
var applicationState = foldp(input, function update(state, modification) {
return modification(state)
}, TodosStructure)
/* Immutable.
*/
var Immutable = require("immutable")
var hash = Immutable.Hash()
var hash2 = hash.patch({ foo: "bar" })
console.log("2", hash2.get("foo"))
var hash3 = hash2.patch("foo", "baz")
console.log("3", hash3.get("foo"))
var hash4 = hash3.patch("foo", null)
console.log("4.1", hash4.get("foo")) // null
console.log("4.2", hash4.hash("foo")) // undefined
var hash5 = hash4.patch({ bar: { baz: true } })
console.log("5.1", hash5.get("bar").toJSON())
console.log("5.2", hash5.get("bar.baz"))
var hash6 = hash5.patch({ bar: { fuux: false } })
console.log("6.1", hash6.get("bar.fuux")) // false
console.log("6.2", hash6.get("bar.baz")) // true
var hash7 = hash6.patch("bar.baz", "hello world")
console.log("7", hash7.get("bar.baz"))
var hash8 = hash7.map("bar", function (x) {
return String(x)
}))
console.log("8", hash8.get("bar.fuux")) // "false"
var hash9 = hash8.patch({ baz: { one: "hello", two: "world" } })
console.log("9", hash9.get("baz").toJSON())
var hash10 = hash9.map(function (x) {
return x.patch("prop", { live: 42 })
})
console.log("10", hash10.toJSON())
// patch :: ImmutableHash -> String path -> Delta -> ImmutableHash
// patch :: ImmutableHash -> Object<String, Delta> -> ImmutableHash
// get :: ImmutableHash -> String path -> Value
// has :: ImmutableHash -> String path -> Boolean
// map :: ImmutableHash<String, A> -> (A -> B) -> ImmutableHash<String, B>
// map :: ImmutableHash -> String path -> (A -> B) -> ImmutableHash
function ImmutableHash() {
return {
patch: patch
, get: get
, has: has
, map: map
}
}
// patch :: Immutable -> String -> Delta -> Immutable
// patch :: Immutable -> Hash<String, Delta> -> Immutable
// patch takes either a String path & a delta and returns a new
// immutable with the patch applied
// Alternative you can give it a Hash of paths & deltas
// A path is a valid string like "key" or "nested.key"
// patch(obj, { key: null }) is delete
// patch(obj, { nonExistant: value }) is add
// patch(obj, { existant: delta }) will merge the delta into
// the existing object
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment